Как изящнее реализовать банковское округление?
От: Marty Пират https://www.youtube.com/channel/UChp5PpQ6T4-93HbNF-8vSYg
Дата: 15.03.21 20:17
Оценка:
Здравствуйте!

Собсно, если кто не помнит — https://en.wikipedia.org/wiki/Rounding#Round_half_to_even
Ну, и парнокопытноепарное к нему — https://en.wikipedia.org/wiki/Rounding#Round_half_to_odd

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

Числа у меня свои, с фиксированной точностью, но точка — плавает Но только влево-вправо, а не по экспоненте. Число в виде делимое/делитель, где делитель — это 10 в какой-то степени. Делимое — целое знаковое, делитель — целое беззнаковое

Есть методы:
  Скрытый текст
void precisionExpandTo( precision_t p ); // расширяем точность, ничего не теряем по дороге
void precisionShrinkTo( precision_t p ); // уменьшаем точность, что упало, то пропало - просто обрезаем
void precisionFitTo( precision_t p );    // тут как пойдёт

unum_t getLowestDecimalDigit() const;    // наименьшая десятичная циферка - для 3.14159 - это 9
bool isDigitEven( unum_t d );
bool isDigitOdd( unum_t d );
void replaceLowestDecimalDigit( unum_t d );


// Тут может стоит поменять Minimal и Precision местами
Decimal makeMinimalPrecisionOne() const;  // Единичка самого младшего разряда текущей точности - для 3.14159 это 0.00001
Decimal makeMinimalPrecisionFive() const; // Пятёрка самого младшего разряда текущей точности  - для 3.14159 это 0.00005


Обычное (математическое) округление выглядит так:
  Скрытый текст
            case RoundingMethod::roundHalfTowardsInf: // roundHalfAwayFromZero, roundMath
                 {
                     precisionFitTo(requestedPrecision + 1);
                     *this += makeMinimalPrecisionFive() * sign();
                     precisionShrinkTo( requestedPrecision );
                 }
                 break;


Floor/Ceil/Trunc:
  Скрытый текст
            case RoundingMethod::roundDown: // roundFloor
                 {
                     precisionFitTo(requestedPrecision + 1);

                     unum_t ldd = getLowestDecimalDigit();
                     precisionShrinkTo( requestedPrecision );

                     if (ldd==0)
                         break;

                     if (sign()<0)
                     {
                         *this -= makeMinimalPrecisionOne();
                     }
                 }
                 break;
        
            case RoundingMethod::roundUp: // roundCeil
                 {
                     precisionFitTo(requestedPrecision + 1);
                     unum_t ldd = getLowestDecimalDigit();
                     precisionShrinkTo( requestedPrecision );

                     if (ldd==0)
                         break;

                     if (sign()>0)
                     {
                         *this += makeMinimalPrecisionOne();
                     }
                 }
                 break;
        
            case RoundingMethod::roundTowardsZero: // roundAwayFromInf, roundTrunc
                 {
                     precisionFitTo(requestedPrecision + 1);
                     precisionShrinkTo( requestedPrecision );
                 }
                 break;


Пока всё, что рожаю, выглядит как кусок говна.

Но наверное тоже можно просто и изящно сделать?
Маньяк Робокряк колесит по городу
Re: Как изящнее реализовать банковское округление?
От: Буравчик Россия  
Дата: 15.03.21 20:38
Оценка: 6 (1)
Здравствуйте, Marty, Вы писали:

M>Но наверное тоже можно просто и изящно сделать?


https://stackoverflow.com/questions/52165539/how-to-round-to-nearest-even-integer

double RoundToNearestEven(double value) =>
    Math.Truncate(value) + Math.Truncate(value) % 2;
Best regards, Буравчик
Re[2]: Как изящнее реализовать банковское округление?
От: Marty Пират https://www.youtube.com/channel/UChp5PpQ6T4-93HbNF-8vSYg
Дата: 15.03.21 20:45
Оценка:
Здравствуйте, Буравчик, Вы писали:

M>>Но наверное тоже можно просто и изящно сделать?


Б>https://stackoverflow.com/questions/52165539/how-to-round-to-nearest-even-integer


Б>
Б>double RoundToNearestEven(double value) =>
Б>    Math.Truncate(value) + Math.Truncate(value) % 2;
Б>


Это до целого, видимо?

А если я хочу 3.1415 по банковски округлить до 3.142?
Маньяк Робокряк колесит по городу
Re: Как изящнее реализовать банковское округление?
От: netch80 Украина http://netch80.dreamwidth.org/
Дата: 15.03.21 20:59
Оценка: 10 (2)
Здравствуйте, Marty, Вы писали:

M>Собсно, если кто не помнит — https://en.wikipedia.org/wiki/Rounding#Round_half_to_even

M>Ну, и парнокопытноепарное к нему — https://en.wikipedia.org/wiki/Rounding#Round_half_to_odd

Если у тебя десятичка, то round half to odd практически бесполезно, вместо него надо брать фоннеймановское округление, которое для десятичной арифметики описывается так:

Round to prepare for shorter precision: the candidate that is smaller in magnitude is selected, unless its voting digit has a value of either 0 or 5; in that case, the candidate that is greater in magnitude is selected.


В вики почему-то тут существенные нюансы пропущены. Voting digit — это минимальная из оставляемых в мантиссе цифр после уплотнения к допустимому количеству цифр (но ещё до финальной нормализации, если она делается).

M>Пока всё, что рожаю, выглядит как кусок говна.

M>Но наверное тоже можно просто и изящно сделать?

Пока что я увидел, что твой precisionShrinkTo тупо урезает хвост — так?
Доступные образцы используют jamming shift. (Надо поискать, как его для десятички использовать.)
Примерно так: сначала урезать так, чтобы было минимум 2 цифры поверх целевой точности, при этом, если урезанная часть не 0, а младшая из оставшихся 0, то заменяешь её на 1.
После этого смотришь на эти две дополнительные цифры: 00-49 — удаляешь; 51-99 — удаляешь с увеличением оставшихся на 1; 50 — смотришь на чётность остающейся цифры.

Если описывать всё это в округлении до целого — получается так:
1.49999 -> 1.49 -> 1
1.50000 -> 1.50 -> 2
1.50001 -> 1.51 -> 2
2.49999 -> 2.49 -> 2
2.50000 -> 2.50 -> 2
2.50001 -> 2.51 -> 3

Не думаю, что есть метод сильно проще. UPD: хм, кажется, и одной цифры хватит, но если jamming shift реализовывать как "5 менять на 6, если отброшенный хвост ненулевой".

M>void precisionShrinkTo( precision_t p ); // уменьшаем точность, что упало, то пропало — просто обрезаем

M>void precisionFitTo( precision_t p ); // тут как пойдёт

Я бы тут в API сразу вписал и метод округления...
The God is real, unless declared integer.
Отредактировано 15.03.2021 21:06 netch80 . Предыдущая версия .
Re[3]: Как изящнее реализовать банковское округление?
От: Буравчик Россия  
Дата: 15.03.21 21:01
Оценка:
Здравствуйте, Marty, Вы писали:

M>А если я хочу 3.1415 по банковски округлить до 3.142?


Сдвинуть точку на три позиции вправо, округлить, сдвинуть обратно
Best regards, Буравчик
Re[2]: Как изящнее реализовать банковское округление?
От: Marty Пират https://www.youtube.com/channel/UChp5PpQ6T4-93HbNF-8vSYg
Дата: 15.03.21 21:18
Оценка:
Здравствуйте, Буравчик, Вы писали:

M>>Но наверное тоже можно просто и изящно сделать?


Б>https://stackoverflow.com/questions/52165539/how-to-round-to-nearest-even-integer


Б>
Б>double RoundToNearestEven(double value) =>
Б>    Math.Truncate(value) + Math.Truncate(value) % 2;
Б>


Запилил:
  Скрытый текст
            case RoundingMethod::roundHalfToEven: // roundBankers, roundBanking
                 {
                     precisionFitTo(requestedPrecision + 1); // общее для остального, тут не нужно, но если вынесу за скобки - то всё равно будет отрабатывать до
                     precisionFitTo(requestedPrecision);

                     int     thisSign     = sign();
                     Decimal thisAbs      = abs();
                     Decimal thisAbsMod2  = abs();
                     thisAbsMod2 = thisAbsMod2.mod(makeMinimalPrecisionTwo());
                     *this = (thisAbs+thisAbsMod2) * thisSign;

                 }
                 break;


Тестовый вывод:
[+]  23.5 rounded to 0 signs with roundHalfToEven is 24
[+]  24.5 rounded to 0 signs with roundHalfToEven is 24
[+]  -23.5 rounded to 0 signs with roundHalfToEven is -24
[+]  -24.5 rounded to 0 signs with roundHalfToEven is -24


Хмм. Похоже — работает. Магия. Потом как-нить потумкаю, как оно так получается. Ну и надо будет проверить не 0.5 случаи

Спасибо.

Ещё бы немного магии для roundHalfToOdd
Но это наверное сам придумаю, если раскурю сработавшее решение. Да и roundHalfToOdd — не особо и нужен, так, чисто для кучи
Маньяк Робокряк колесит по городу
Re[2]: Как изящнее реализовать банковское округление?
От: Marty Пират https://www.youtube.com/channel/UChp5PpQ6T4-93HbNF-8vSYg
Дата: 15.03.21 21:32
Оценка:
Здравствуйте, netch80, Вы писали:

M>>Собсно, если кто не помнит — https://en.wikipedia.org/wiki/Rounding#Round_half_to_even

M>>Ну, и парнокопытноепарное к нему — https://en.wikipedia.org/wiki/Rounding#Round_half_to_odd

N>Если у тебя десятичка, то round half to odd практически бесполезно, вместо него надо брать фоннеймановское округление, которое для десятичной арифметики описывается так:


Да х его знает, меня интересовали floor, ceil, trunc, math и банковское. Остальное решил запилить за кампанию — чтобы было


N>Пока что я увидел, что твой precisionShrinkTo тупо урезает хвост — так?


Ну да. Чисто технологическая внутренняя операция


N>Доступные образцы используют jamming shift. (Надо поискать, как его для десятички использовать.)

N>Примерно так: сначала урезать так, чтобы было минимум 2 цифры поверх целевой точности, при этом, если урезанная часть не 0, а младшая из оставшихся 0, то заменяешь её на 1.
N>После этого смотришь на эти две дополнительные цифры: 00-49 — удаляешь; 51-99 — удаляешь с увеличением оставшихся на 1; 50 — смотришь на чётность остающейся цифры.

Спс, покурю как-нить на досуге. Буравчиковое решение со стек оверфлов тупо закодил и вроде работает. Пока доволен


M>>void precisionShrinkTo( precision_t p ); // уменьшаем точность, что упало, то пропало — просто обрезаем

M>>void precisionFitTo( precision_t p ); // тут как пойдёт

N>Я бы тут в API сразу вписал и метод округления...


Дык это protected технологические методы, в публичное апи они не попадают, а вот округление — уже в публичном апи, и принимает точность и способ округления
Маньяк Робокряк колесит по городу
Re[4]: Как изящнее реализовать банковское округление?
От: Marty Пират https://www.youtube.com/channel/UChp5PpQ6T4-93HbNF-8vSYg
Дата: 15.03.21 21:35
Оценка:
Здравствуйте, Буравчик, Вы писали:

M>>А если я хочу 3.1415 по банковски округлить до 3.142?


Б>Сдвинуть точку на три позиции вправо, округлить, сдвинуть обратно


Округлить — как?

ЗЫ Пример неудачный привёл. По математическому округлению так же получится
Маньяк Робокряк колесит по городу
Re[3]: Как изящнее реализовать банковское округление?
От: netch80 Украина http://netch80.dreamwidth.org/
Дата: 16.03.21 06:41
Оценка:
Здравствуйте, Marty, Вы писали:

M>Да х его знает, меня интересовали floor, ceil, trunc, math и банковское. Остальное решил запилить за кампанию — чтобы было


RfSP нужно для поддержки двойных и более округлений: каждое, кроме финального, делать через него.
Иначе, например, 1.499999 сначала округлится обычным RfE до 1.500, а потом до 2, хотя напрямую должно было быть до 1. Через RfSP тебе округлит до 1.499, а дальше, понятно, RfE до 1.

M>Спс, покурю как-нить на досуге. Буравчиковое решение со стек оверфлов тупо закодил и вроде работает. Пока доволен


Я не понял по коду, что именно ты там таки запилил, но ты проверил не на 0.5 случаи?
Первые два precisionFitTo с усечением(?) два подряд — нужны ли они?
В общем, пока все случаи не проверишь... я бы не утверждал, что работает.
Я так навскидку посмотрел:
x = 23.1
truncate(x) == 23
truncate(x)%2 == 1
в сумме 24, а нужно 23.
The God is real, unless declared integer.
Отредактировано 17.03.2021 10:32 netch80 . Предыдущая версия .
Re: Как изящнее реализовать банковское округление?
От: vmpire Россия  
Дата: 16.03.21 11:49
Оценка:
Здравствуйте, Marty, Вы писали:

M>Здравствуйте!


M>Собсно, если кто не помнит — https://en.wikipedia.org/wiki/Rounding#Round_half_to_even

M>Ну, и парнокопытноепарное к нему — https://en.wikipedia.org/wiki/Rounding#Round_half_to_odd

M>Все округления, что выше — реализовал, всё вышло довольно просто, а тут что-то затупил.

Я понимаю, что это раздел "алгоритмы", но ведь наверняка уже есть что-то готовое, зачем изобретать велосипед?
В .NET, например: Math.Round(amount, 1, MidpointRounding.ToEven)
Кстати, можно и его исходники посмотреть, если интересует алгоритм
Re[4]: Как изящнее реализовать банковское округление?
От: sergii.p  
Дата: 16.03.21 14:34
Оценка:
Здравствуйте, netch80, Вы писали:

N>в сумме 24, а нужно 23.


это почему? Там же ясно написано ToNearestEven. К ближайшему чётному.
Re[5]: Как изящнее реализовать банковское округление?
От: watchmaker  
Дата: 16.03.21 16:59
Оценка: 6 (1) +2
Здравствуйте, sergii.p, Вы писали:

SP>Здравствуйте, netch80, Вы писали:


N>>в сумме 24, а нужно 23.


SP>это почему?


Потому что в теме и в первом сообщении так написано: нужно "реализовать банковское округление". И ссылка дана https://en.wikipedia.org/wiki/Rounding#Round_half_to_even

SP>Там же ясно написано ToNearestEven. К ближайшему чётному.


Да, по ссылке Буравчика на stackoverflow идёт округление именно что к ближайшему чётному.
И это ни разу не банковский способ округления. Это даже не округление к ближайшему целому. То есть это просто другая задача.


В банковском округлении ты округляешь число к ближайшему целому, а в случае неоднозначности — к ближайшему чётному.
По ссылке же "округление" идёт всегда к ближайшему чётному (даже если исходный аргумент уже является целым числом). А в случае неоднзначности (когда есть два равноудалённых чётных числа, например как у числа 1023) — в сторону от нуля.
Re[5]: Как изящнее реализовать банковское округление?
От: netch80 Украина http://netch80.dreamwidth.org/
Дата: 16.03.21 17:43
Оценка:
Здравствуйте, sergii.p, Вы писали:

SP>Здравствуйте, netch80, Вы писали:


N>>в сумме 24, а нужно 23.


SP>это почему? Там же ясно написано ToNearestEven. К ближайшему чётному.


Полностью это округление зовётся "round to nearest, ties to even" (термин по IEEE754). То есть округление к ближайшему целому, а дополнительное условие — к чётному — вводится только если два кандидата (к нулю и от нуля) расположены на равном расстоянии.

Топикстартер в курсе, а вам следовало бы сходить хотя бы на ту же вики-страничку.
The God is real, unless declared integer.
Re[2]: Как изящнее реализовать банковское округление?
От: aspb  
Дата: 16.03.21 18:13
Оценка:
Здравствуйте, vmpire, Вы писали:

V>В .NET, например: Math.Round(amount, 1, MidpointRounding.ToEven)

V>Кстати, можно и его исходники посмотреть, если интересует алгоритм

Но что самое страшное — MidpointRounding.ToEven он в .net по умолчанию, т.е. можно просто Math.Round(amount, 1)
Re[2]: Как изящнее реализовать банковское округление?
От: netch80 Украина http://netch80.dreamwidth.org/
Дата: 16.03.21 18:43
Оценка:
Здравствуйте, vmpire, Вы писали:

V>Я понимаю, что это раздел "алгоритмы", но ведь наверняка уже есть что-то готовое, зачем изобретать велосипед?

V>В .NET, например: Math.Round(amount, 1, MidpointRounding.ToEven)
V>Кстати, можно и его исходники посмотреть, если интересует алгоритм

Ну я дал ссылку на Berkeley Softfloat.
А вот в дотнете слишком завязано на особенности внутренней реализации Decimal и потому напрямую не переносится. Но там по сути делают так же, как я описал: sticky bits собирают признак наличия значения свыше 0.5.
The God is real, unless declared integer.
Отредактировано 16.03.2021 19:04 netch80 . Предыдущая версия .
Re[4]: Как изящнее реализовать банковское округление?
От: Marty Пират https://www.youtube.com/channel/UChp5PpQ6T4-93HbNF-8vSYg
Дата: 16.03.21 19:40
Оценка:
Здравствуйте, netch80, Вы писали:

N>Я не понял по коду, что именно ты там таки запилил, но ты проверил не на 0.5 случаи?


Не, не все. Сейчас тестик допишу, проверю. Но если и не работает для не 0.5 случаев, то это элементарно решается проверкой младшей цифры на 5 и выделением этого случая


N>Первые два precisionFitTo с усечением(?) два подряд — нужны ли они?


Нет, не нужны. Первое p+1 усечение лишнее, но оно не мешает. А в остальных способах оно нужно, я его потом перенесу в начало функции


N>В общем, пока все случаи не проверишь... я бы не утверждал, что работает.

N>Я так навскидку посмотрел:
N>x = 23.1
N>truncate(x) == 23
N>truncate(x)%2 == 1
N>в сумме 24, а нужно 23.

Да, спасибо, что подсказал. Я тестил в основном на поведение при 0.5, допилил, вроде всё работает (ну, если я правильно посчитал, что должно получиться в итоге).

  Результаты тестов
[quote]------------------------------
[+] 23.0 rounded to 0 signs with roundFloor rounding method is 23
[+] 23.7 rounded to 0 signs with roundFloor rounding method is 23
[+] 24.0 rounded to 0 signs with roundFloor rounding method is 24

[+] -23.0 rounded to 0 signs with roundFloor rounding method is -23
[+] -23.2 rounded to 0 signs with roundFloor rounding method is -24
[+] -24.0 rounded to 0 signs with roundFloor rounding method is -24
------------------------------
[+] 23.0 rounded to 0 signs with roundCeil rounding method is 23
[+] 23.2 rounded to 0 signs with roundCeil rounding method is 24
[+] 24.0 rounded to 0 signs with roundCeil rounding method is 24

[+] -23.0 rounded to 0 signs with roundCeil rounding method is -23
[+] -23.7 rounded to 0 signs with roundCeil rounding method is -23
[+] -24.0 rounded to 0 signs with roundCeil rounding method is -24
------------------------------
[+] 23.0 rounded to 0 signs with roundTrunc rounding method is 23
[+] 23.7 rounded to 0 signs with roundTrunc rounding method is 23
[+] 24.0 rounded to 0 signs with roundTrunc rounding method is 24

[+] -23.0 rounded to 0 signs with roundTrunc rounding method is -23
[+] -23.7 rounded to 0 signs with roundTrunc rounding method is -23
[+] -24.0 rounded to 0 signs with roundTrunc rounding method is -24
------------------------------
[+] 23.0 rounded to 0 signs with roundTowardsInf rounding method is 23
[+] 23.2 rounded to 0 signs with roundTowardsInf rounding method is 24
[+] 24.0 rounded to 0 signs with roundTowardsInf rounding method is 24

[+] -23.0 rounded to 0 signs with roundTowardsInf rounding method is -23
[+] -23.2 rounded to 0 signs with roundTowardsInf rounding method is -24
[+] -24.0 rounded to 0 signs with roundTowardsInf rounding method is -24
------------------------------
[+] 23.1 rounded to 0 signs with roundHalfUp rounding method is 23
[+] 23.5 rounded to 0 signs with roundHalfUp rounding method is 24
[+] 23.9 rounded to 0 signs with roundHalfUp rounding method is 24

[+] -23.1 rounded to 0 signs with roundHalfUp rounding method is -23
[+] -23.5 rounded to 0 signs with roundHalfUp rounding method is -23
[+] -23.9 rounded to 0 signs with roundHalfUp rounding method is -24
------------------------------
[+] 23.1 rounded to 0 signs with roundHalfDown rounding method is 23
[+] 23.5 rounded to 0 signs with roundHalfDown rounding method is 23
[+] 23.9 rounded to 0 signs with roundHalfDown rounding method is 24

[+] -23.1 rounded to 0 signs with roundHalfDown rounding method is -23
[+] -23.5 rounded to 0 signs with roundHalfDown rounding method is -24
[+] -23.9 rounded to 0 signs with roundHalfDown rounding method is -24
------------------------------
[+] 23.1 rounded to 0 signs with roundHalfTowardsZero rounding method is 23
[+] 23.5 rounded to 0 signs with roundHalfTowardsZero rounding method is 23
[+] 23.9 rounded to 0 signs with roundHalfTowardsZero rounding method is 24

[+] -23.1 rounded to 0 signs with roundHalfTowardsZero rounding method is -23
[+] -23.5 rounded to 0 signs with roundHalfTowardsZero rounding method is -23
[+] -23.9 rounded to 0 signs with roundHalfTowardsZero rounding method is -24
------------------------------
[+] 23.1 rounded to 0 signs with roundHalfTowardsInf rounding method is 23
[+] 23.5 rounded to 0 signs with roundHalfTowardsInf rounding method is 24
[+] 23.9 rounded to 0 signs with roundHalfTowardsInf rounding method is 24

[+] -23.1 rounded to 0 signs with roundHalfTowardsInf rounding method is -23
[+] -23.5 rounded to 0 signs with roundHalfTowardsInf rounding method is -24
[+] -23.9 rounded to 0 signs with roundHalfTowardsInf rounding method is -24
------------------------------
[+] 23.1 rounded to 0 signs with roundHalfToEven rounding method is 23
[+] 23.5 rounded to 0 signs with roundHalfToEven rounding method is 24
[+] 23.9 rounded to 0 signs with roundHalfToEven rounding method is 24

[+] 24.1 rounded to 0 signs with roundHalfToEven rounding method is 24
[+] 24.5 rounded to 0 signs with roundHalfToEven rounding method is 24
[+] 24.9 rounded to 0 signs with roundHalfToEven rounding method is 25

[+] -23.1 rounded to 0 signs with roundHalfToEven rounding method is -23
[+] -23.5 rounded to 0 signs with roundHalfToEven rounding method is -24
[+] -23.9 rounded to 0 signs with roundHalfToEven rounding method is -24

[+] -24.1 rounded to 0 signs with roundHalfToEven rounding method is -24
[+] -24.5 rounded to 0 signs with roundHalfToEven rounding method is -24
[+] -24.9 rounded to 0 signs with roundHalfToEven rounding method is -25

------------------------------

Failed 0 tests from total 60
+++ All rounding tests passed[/quote]

Может кто заметит, что я не протестил

  Тесты выглядят так (доморощенное, не гугл тест и всё такое)
    #define ROUNDING_TEST( dblVal, strResForCompare, roundingPrecision, roundingMethod ) \
                do                                                                       \
                {                                                                        \
                    ++totalRoundingTests;                                                \
                    Decimal decimal    = Decimal(dblVal);                                \
                    Decimal roundedVal = decimal.rounded( roundingPrecision, Decimal::RoundingMethod::roundingMethod ); \
                    /*std::ostringstream os;*/                                           \
                    std::string strRes = roundedVal.toString(roundingPrecision);         \
                                                                                         \
                    bool bGood /* Johny */ = (strRes==strResForCompare);                 \
                    if (!bGood)                                                          \
                       ++totalRoundingTestsFailed;                                       \
                                                                                         \
                                                                                         \
                    cout << "[" << (bGood ? "+" : "-") << "]  " << (decimal<0 ? "" : " ") << decimal.toString(roundingPrecision+1) << " rounded to " << roundingPrecision << " signs with " << #roundingMethod << " rounding method is " << strRes; \
                    if (!bGood)                                                          \
                    {                                                                    \
                        cout << " (expected " << strResForCompare << ")";                \
                    }                                                                    \
                    cout << endl;                                                        \
                                                                                         \
                } while(0)


    // https://en.wikipedia.org/wiki/Rounding#Round_half_to_odd

    cout << "------------------------------" << endl;
    ROUNDING_TEST(   23.0,  "23", 0, roundFloor           );
    ROUNDING_TEST(   23.7,  "23", 0, roundFloor           );
    ROUNDING_TEST(   24.0,  "24", 0, roundFloor           );

    cout << endl;
    ROUNDING_TEST(  -23.0, "-23", 0, roundFloor           );
    ROUNDING_TEST(  -23.2, "-24", 0, roundFloor           );
    ROUNDING_TEST(  -24.0, "-24", 0, roundFloor           );

    cout << "------------------------------" << endl;
    ROUNDING_TEST(   23.0,  "23", 0, roundCeil            );
    ROUNDING_TEST(   23.2,  "24", 0, roundCeil            );
    ROUNDING_TEST(   24.0,  "24", 0, roundCeil            );

    cout << endl;
    ROUNDING_TEST(  -23.0, "-23", 0, roundCeil            );
    ROUNDING_TEST(  -23.7, "-23", 0, roundCeil            );
    ROUNDING_TEST(  -24.0, "-24", 0, roundCeil            );

    cout << "------------------------------" << endl;
    ROUNDING_TEST(   23.0,  "23", 0, roundTrunc           ); // roundTowardsZero
    ROUNDING_TEST(   23.7,  "23", 0, roundTrunc           ); // roundTowardsZero
    ROUNDING_TEST(   24.0,  "24", 0, roundTrunc           ); // roundTowardsZero

    cout << endl;
    ROUNDING_TEST(  -23.0, "-23", 0, roundTrunc           ); // roundTowardsZero
    ROUNDING_TEST(  -23.7, "-23", 0, roundTrunc           ); // roundTowardsZero
    ROUNDING_TEST(  -24.0, "-24", 0, roundTrunc           ); // roundTowardsZero

    cout << "------------------------------" << endl;
    ROUNDING_TEST(   23.0,  "23", 0, roundTowardsInf      ); // roundAwayFromZero
    ROUNDING_TEST(   23.2,  "24", 0, roundTowardsInf      ); // roundAwayFromZero
    ROUNDING_TEST(   24.0,  "24", 0, roundTowardsInf      ); // roundAwayFromZero

    cout << endl;
    ROUNDING_TEST(  -23.0, "-23", 0, roundTowardsInf      ); // roundAwayFromZero
    ROUNDING_TEST(  -23.2, "-24", 0, roundTowardsInf      ); // roundAwayFromZero
    ROUNDING_TEST(  -24.0, "-24", 0, roundTowardsInf      ); // roundAwayFromZero

    cout << "------------------------------" << endl;
    ROUNDING_TEST(   23.1,  "23", 0, roundHalfUp          );
    ROUNDING_TEST(   23.5,  "24", 0, roundHalfUp          );
    ROUNDING_TEST(   23.9,  "24", 0, roundHalfUp          );

    cout << endl;
    ROUNDING_TEST(  -23.1, "-23", 0, roundHalfUp          );
    ROUNDING_TEST(  -23.5, "-23", 0, roundHalfUp          );
    ROUNDING_TEST(  -23.9, "-24", 0, roundHalfUp          );

    cout << "------------------------------" << endl;
    ROUNDING_TEST(   23.1,  "23", 0, roundHalfDown        );
    ROUNDING_TEST(   23.5,  "23", 0, roundHalfDown        );
    ROUNDING_TEST(   23.9,  "24", 0, roundHalfDown        );
    cout << endl;
    ROUNDING_TEST(  -23.1, "-23", 0, roundHalfDown        );
    ROUNDING_TEST(  -23.5, "-24", 0, roundHalfDown        );
    ROUNDING_TEST(  -23.9, "-24", 0, roundHalfDown        );

    cout << "------------------------------" << endl;
    ROUNDING_TEST(   23.1,  "23", 0, roundHalfTowardsZero );
    ROUNDING_TEST(   23.5,  "23", 0, roundHalfTowardsZero );
    ROUNDING_TEST(   23.9,  "24", 0, roundHalfTowardsZero );

    cout << endl;
    ROUNDING_TEST(  -23.1, "-23", 0, roundHalfTowardsZero );
    ROUNDING_TEST(  -23.5, "-23", 0, roundHalfTowardsZero );
    ROUNDING_TEST(  -23.9, "-24", 0, roundHalfTowardsZero );

    cout << "------------------------------" << endl;
    ROUNDING_TEST(   23.1,  "23", 0, roundHalfTowardsInf  ); // roundMath
    ROUNDING_TEST(   23.5,  "24", 0, roundHalfTowardsInf  );
    ROUNDING_TEST(   23.9,  "24", 0, roundHalfTowardsInf  );

    cout << endl;
    ROUNDING_TEST(  -23.1, "-23", 0, roundHalfTowardsInf  );
    ROUNDING_TEST(  -23.5, "-24", 0, roundHalfTowardsInf  );
    ROUNDING_TEST(  -23.9, "-24", 0, roundHalfTowardsInf  );

    cout << "------------------------------" << endl;
    ROUNDING_TEST(   23.1,  "23", 0, roundHalfToEven      );
    ROUNDING_TEST(   23.5,  "24", 0, roundHalfToEven      );
    ROUNDING_TEST(   23.9,  "24", 0, roundHalfToEven      );

    cout << endl;
    ROUNDING_TEST(   24.1,  "24", 0, roundHalfToEven      );
    ROUNDING_TEST(   24.5,  "24", 0, roundHalfToEven      );
    ROUNDING_TEST(   24.9,  "25", 0, roundHalfToEven      );

    cout << endl;
    ROUNDING_TEST(  -23.1, "-23", 0, roundHalfToEven      );
    ROUNDING_TEST(  -23.5, "-24", 0, roundHalfToEven      );
    ROUNDING_TEST(  -23.9, "-24", 0, roundHalfToEven      );

    cout << endl;
    ROUNDING_TEST(  -24.1, "-24", 0, roundHalfToEven      );
    ROUNDING_TEST(  -24.5, "-24", 0, roundHalfToEven      );
    ROUNDING_TEST(  -24.9, "-25", 0, roundHalfToEven      );

    /*
    cout << "------------------------------" << endl;
    ROUNDING_TEST(   23.5,  "23", 0, roundHalfToOdd       );
    ROUNDING_TEST(   22.5,  "23", 0, roundHalfToOdd       );
    ROUNDING_TEST(  -23.5, "-23", 0, roundHalfToOdd       );
    ROUNDING_TEST(  -22.5, "-23", 0, roundHalfToOdd       );
    */

    cout << endl;
    cout << "------------------------------" << endl;
    cout << endl;

    cout << "Failed " << totalRoundingTestsFailed << " tests from total " << totalRoundingTests << endl;
    if (!totalRoundingTestsFailed)
        cout << "+++ All rounding tests passed"  << endl;

  Собсно, код для всех вариантов округления
    Decimal& roundingImpl( precision_t requestedPrecision, RoundingMethod roundingMethod )
    {
        // Нужно привести точность к precision+1
        // Если необходимо, то расширить точность
        // Нужно проверить, возможно ли такое расширение

        // Если невозможно расширение точности, 
        // то: а) вернуть, как есть
        //     б) кинуть исключение
        // Наверное - (а), так как если точность меньше требуемой, то и округления никакого не нужно

        // В итоге - если текущая точность меньше или равна запрошенной, то ничего делать и не надо 

        //precision_t curPrecision = m_denum.precision();
        if ( m_denum.precision() <= requestedPrecision )
            return *this;

        // if (findMaxDecimalScalePower()<1) // maxAllowedPrecisionIncrement < 1
        //     return *this;

        // Decimal makeMinimalPrecisionOne()
        // Decimal makeMinimalPrecisionFive()
        // unum_t  getLowestDecimalDigit()

        precisionFitTo(requestedPrecision + 1);
        unum_t ldd = getLowestDecimalDigit();


        switch(roundingMethod)
        {
            case RoundingMethod::roundDown: // roundFloor
                 {
                     precisionShrinkTo( requestedPrecision );

                     if (ldd==0)
                         break;

                     if (sign()<0)
                     {
                         *this -= makeMinimalPrecisionOne();
                     }
                 }
                 break;
        
            case RoundingMethod::roundUp: // roundCeil
                 {
                     precisionShrinkTo( requestedPrecision );

                     if (ldd==0)
                         break;

                     if (sign()>0)
                     {
                         *this += makeMinimalPrecisionOne();
                     }
                 }
                 break;
        
            case RoundingMethod::roundTowardsZero: // roundAwayFromInf, roundTrunc
                 {
                     precisionShrinkTo( requestedPrecision );
                 }
                 break;
        
            case RoundingMethod::roundTowardsInf: // roundAwayFromZero
                 {
                     precisionShrinkTo( requestedPrecision );
                     *this += makeMinimalPrecisionOne() * sign();
                 }
                 break;
        
            case RoundingMethod::roundHalfUp: // roundHalfTowardsPositiveInf
                 {
                     if (ldd==5 || sgn()>0)
                         *this += makeMinimalPrecisionFive();
                     else if (ldd>=5 || sgn()<0)
                         *this -= makeMinimalPrecisionFive();
                     else
                         { /* simple truncation */ }

                     precisionShrinkTo( requestedPrecision );
                 }
                 break;

            case RoundingMethod::roundHalfDown: // roundHalfTowardsNegativeInf
                 {
                     if (ldd==5 /* || sgn()>0 */ )
                         *this -= makeMinimalPrecisionFive(); // Ok
                     else if (ldd>=5)
                         *this += makeMinimalPrecisionFive() * sgn();
                     else
                         { /* simple truncation */ }

                     precisionShrinkTo( requestedPrecision );
                 }
                 break;
        
            case RoundingMethod::roundHalfTowardsZero: // roundHalfAwayFromInf
                 {
                     if (ldd<=5)
                         { /* simple truncation */ }
                     else
                     {
                         *this += makeMinimalPrecisionFive() * sgn();
                     }

                     //*this -= makeMinimalPrecisionFive() * sign();
                     precisionShrinkTo( requestedPrecision );
                 }
                 break;
        
            case RoundingMethod::roundHalfTowardsInf: // roundHalfAwayFromZero, roundMath
                 {
                     *this += makeMinimalPrecisionFive() * sign();
                     precisionShrinkTo( requestedPrecision );
                 }
                 break;
        
            case RoundingMethod::roundHalfToEven: // roundBankers, roundBanking
                 {
                     precisionFitTo(requestedPrecision);

                     if (ldd==5)
                     {
                         int     thisSign     = sign();
                         Decimal thisAbs      = abs();
                         Decimal thisAbsMod2  = abs();
                         thisAbsMod2 = thisAbsMod2.mod(makeMinimalPrecisionTwo());
                         *this = (thisAbs+thisAbsMod2) * thisSign;
                     }
                     else if (ldd>5)
                     {
                         *this += makeMinimalPrecisionOne() * sign();
                     }
                 }
                 break;
        
            case RoundingMethod::roundHalfToOdd:
                 {
                     //throw std::runtime_error("RoundingMethod::roundHalfToOdd not implemented");

                     // Soryan, not implemented yet
                     precisionFitTo(requestedPrecision);
                     // Currently is the same as "trunc"

                 }
                 break;
        
        
        };

        return *this;
    }
Маньяк Робокряк колесит по городу
Re[2]: Как изящнее реализовать банковское округление?
От: Marty Пират https://www.youtube.com/channel/UChp5PpQ6T4-93HbNF-8vSYg
Дата: 16.03.21 20:09
Оценка:
Здравствуйте, vmpire, Вы писали:

M>>Собсно, если кто не помнит — https://en.wikipedia.org/wiki/Rounding#Round_half_to_even

M>>Ну, и парнокопытноепарное к нему — https://en.wikipedia.org/wiki/Rounding#Round_half_to_odd

M>>Все округления, что выше — реализовал, всё вышло довольно просто, а тут что-то затупил.

V>Я понимаю, что это раздел "алгоритмы", но ведь наверняка уже есть что-то готовое, зачем изобретать велосипед?

Потому что могу

А так — нужен был класс для чисел с плавающей фикс точкой, ни C++ std, ни Qt ничего предложить не смогли. Буст не хотел присовывать — принцип минимума зависимостей. Ну и в контроллер буст не всегда запихаешь — можно умучаться, отрезая лишнее, а потом всё равно переписать всё заново.


V>В .NET, например: Math.Round(amount, 1, MidpointRounding.ToEven)

V>Кстати, можно и его исходники посмотреть, если интересует алгоритм

Ну, мне, не знакомому с инфраструктурой дотнета, быстрее наверно таки самому было написать, чем разбираться, где посмотреть исходники Math.Round'а (это ещё бы если я знал, что такое есть)
Маньяк Робокряк колесит по городу
Re[5]: Как изящнее реализовать банковское округление?
От: netch80 Украина http://netch80.dreamwidth.org/
Дата: 17.03.21 07:11
Оценка: 6 (1)
Здравствуйте, Marty, Вы писали:

M>Может кто заметит, что я не протестил


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

M> precisionFitTo(requestedPrecision + 1);

M> unum_t ldd = getLowestDecimalDigit();

Ну вот тут таки кажется, что ты теряешь вариант типа 23.51, 23.501, 23.5001... этот precisionFitTo, если просто обрубает хвост, то теряет важные цифры на таких пограничных значениях.
Причём это будет касаться не только roundHalfEven, но и, например, roundCeil для 23.01. Первым рывком ты урезаешь до 23.0 отсекая хвост, потом видишь 0 и останавливаешься на 23, когда должно быть 24.

Добавь тесты на значение типа 23.01, 23.001, 23.51, 23.501 и проверь. Для полного набора ещё включить 23.49, 23.499, 23.99, 23.999.

Имеет смысл переделать эту precisionShrinkTo с возвратом, например, bool-признака "было ли ненулевое в отсечённой части" (назову его sticky по традиции). А дальше по этому признаку, например:

bool sticky = precisionShrinkTo(requestedPrecision + 1);
unum_t ldd = getLowestDecimalDigit();
// Обеспечить, чтобы ldd == 5 значило точно случай 0.5, а ldd == 0 - отсутствие необходимости округления.
// Фактически, это дословное round to prepare for shorter precision по описанию IBM.
if (sticky && (ldd == 0 || ldd == 5)) {
  ++ldd;
}
precisionShrinkTo(requestedPrecision); // будем корректировать на 1 от этого значения
if (ldd == 0) {
  // Значение точное, округления не происходит, режим не имеет значения
  return *this;
}
// После этой точки stickу не нужен. ldd не может быть равно 0 (устранено раньше).

switch(roundingMethod)
{
  case RoundingMethod::roundDown: // roundFloor
  {
    if (sgn() < 0) {
      *this -= makeMinimalPrecisionOne();
    }
    break;
  }
  // ... с остальными direct разберёшься по аналогии ...
  case RoundingMethod::roundHalfToInf: // roundHalfTowardsPositiveInf
  {
    if (ldd>=5)
      incrementMantissa();
    break;
  }
  case RoundingMethod::roundHalfToZero:
  {
    if (ldd>5)
      incrementMantissa();
    break;
  }
  // ...
  case RoundingMethod::roundHalfToEven: // roundBankers, roundBanking
  if (ldd > 5 || (ldd==5 && abs() % 2 != 0)))
  {
    incrementMantissa();
  }
  break;
  // ... и так далее ...

// Задолбало выписывать каждый раз.
// TODO Явно можно удешевить, если на знак вообще не смотреть, а работать с мантиссой.
inline void Decimal::incrementMantissa()
{
    if (sign() > 0)
      *this  += makeMinimalPrecisionOne();
    if (sign() < 0)
      *this  -= makeMinimalPrecisionOne();
}


Ещё очень полезно перемапить режимы округления на основании знака и после этого работать только с мантиссой, например:
roundDown, >0 => roundToZero
roundUp, >0 => roundToInf
roundDown, <0 => roundToInf
roundUp, >0 => roundToZero
...
roundHalfUp, >0 => roundHalfToInf
roundHalfDown, >0 => roundHalfToZero
roundHalfUp, <0 => roundHalfToZero
roundHalfDown, <0 => roundHalfToInf
...

а в switch оставить только уменьшенный набор и уже не смотреть на знак.
Это я в том же Berkeley Softfloat увидел.
The God is real, unless declared integer.
Отредактировано 18.03.2021 10:43 netch80 . Предыдущая версия . Еще …
Отредактировано 17.03.2021 7:28 netch80 . Предыдущая версия .
Отредактировано 17.03.2021 7:27 netch80 . Предыдущая версия .
Re[3]: Как изящнее реализовать банковское округление?
От: pagid Россия  
Дата: 17.03.21 11:01
Оценка:
Здравствуйте, Marty, Вы писали:

M>А так — нужен был класс для чисел с плавающей фикс точкой, ни C++ std, ни Qt ничего предложить не смогли. Буст не хотел присовывать — принцип минимума зависимостей. Ну и в контроллер буст не всегда запихаешь — можно умучаться, отрезая лишнее, а потом всё равно переписать всё заново.

Нескромный вопрос и проявление праздного любопытства, но зачем в контроллер банковское округление?
Re[4]: Как изящнее реализовать банковское округление?
От: Marty Пират https://www.youtube.com/channel/UChp5PpQ6T4-93HbNF-8vSYg
Дата: 25.03.21 21:42
Оценка:
Здравствуйте, pagid, Вы писали:

M>>А так — нужен был класс для чисел с плавающей фикс точкой, ни C++ std, ни Qt ничего предложить не смогли. Буст не хотел присовывать — принцип минимума зависимостей. Ну и в контроллер буст не всегда запихаешь — можно умучаться, отрезая лишнее, а потом всё равно переписать всё заново.

P>Нескромный вопрос и проявление праздного любопытства, но зачем в контроллер банковское округление?


Например, какую-нибудь статистику считать. Банковское округление оно не только "банковсеое", но и статистическое.

Ну и просто. Сейчас я по микроконтроллерам, STM32, в основном. Пишу с оглядкой — мало ли что. Потому, что могу
Маньяк Робокряк колесит по городу
Подождите ...
Wait...
Пока на собственное сообщение не было ответов, его можно удалить.