Проблемы чисел с плавающими точками, энтропия

Рейтинг: -1Ответов: 2Опубликовано: 28.07.2023

Представим себе следующую ситуацию

У нас есть 4 столбца

  • Сумма без ндс -> sum_wo_tax
  • Сумма с ндс -> sum_w_tax
  • Определитель, облагается ли ндс -> is_wo_nds
  • Исключена ли данная сумма (учитываем ли мы её, или нет) -> excluded_from_delivery

Мы хотим посчитать коэффициент на вычет исключенных позиций из заказа, есть формула

sum(decode(t.is_wo_nds, 1, t.sum_wo_tax, t.sum_w_tax) * abs(t.excluded_from_delivery)) 
/
sum(decode(t.is_wo_nds, 1, decode(t.sum_wo_tax,0,1,t.sum_wo_tax), decode(t.sum_w_tax,0,1,t.sum_w_tax))) as coef

Давайте протестируем её.

Представим что есть 3 позиции, первая на 100, вторая на 100, третья на 0, первая и третья исключены, получается из суммы на 200 мы вычли 100 рублей, что равно коэффициенту 0.5

select sum(decode(t.is_wo_nds, 1, t.sum_wo_tax, t.sum_w_tax) * abs(t.excluded_from_delivery)) 
/
sum(decode(t.is_wo_nds, 1, decode(t.sum_wo_tax,0,1,t.sum_wo_tax), decode(t.sum_w_tax,0,1,t.sum_w_tax))) as coef
              from (select 0 as is_wo_nds,
                           100 as sum_wo_tax,
                           100 as sum_w_tax,
                           1 as excluded_from_delivery
                           from dual
                    union all
                    select 0 as is_wo_nds,
                           100 as sum_wo_tax,
                           100 as sum_w_tax,
                           0 as excluded_from_delivery
                           from dual
                    union all
                    select 0 as is_wo_nds,
                           0 as sum_wo_tax,
                           0 as sum_w_tax,
                           1 as excluded_from_delivery
                           from dual) t

получаем

coef
.4975124378109452736318407960199004975124

Странно, но должно было получиться 0.5

А если убрать все обработки и мы точно понимаем, что в наших расчётах будет участвовать только sum_w_tax и excluded_from_delivery а то есть

select sum(t.sum_w_tax * abs(t.excluded_from_delivery)) /
                   sum(t.sum_w_tax) as coef
              from (select 0 as is_wo_nds,
                           100 as sum_wo_tax,
                           100 as sum_w_tax,
                           1 as excluded_from_delivery
                           from dual
                    union all
                    select 0 as is_wo_nds,
                           100 as sum_wo_tax,
                           100 as sum_w_tax,
                           0 as excluded_from_delivery
                           from dual
                    union all
                    select 0 as is_wo_nds,
                           0 as sum_wo_tax,
                           0 as sum_w_tax,
                           1 as excluded_from_delivery
                           from dual) t

мы получаем

coef
.5

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

db<>fiddle если нужно

Неужели decode так странно ведет себя?


UPD1

А что если переписать всё под констуркцию case when?

SELECT 
    SUM(CASE 
            WHEN t.is_wo_nds = 1 THEN t.sum_wo_tax * ABS(t.excluded_from_delivery)
            ELSE t.sum_w_tax * ABS(t.excluded_from_delivery)
        END
    ) / 
    SUM(CASE 
            WHEN t.is_wo_nds = 1 THEN 
                CASE 
                    WHEN t.sum_wo_tax = 0 THEN 1
                    ELSE t.sum_wo_tax
                END
            ELSE 
                CASE 
                    WHEN t.sum_w_tax = 0 THEN 1
                    ELSE t.sum_w_tax
                END
        END
    ) as coef
              from (select 0 as is_wo_nds,
                           100 as sum_wo_tax,
                           100 as sum_w_tax,
                           1 as excluded_from_delivery
                           from dual
                    union all
                    select 0 as is_wo_nds,
                           100 as sum_wo_tax,
                           100 as sum_w_tax,
                           0 as excluded_from_delivery
                           from dual
                    union all
                    select 0 as is_wo_nds,
                           0 as sum_wo_tax,
                           0 as sum_w_tax,
                           1 as excluded_from_delivery
                           from dual) t

И результат тот же

coef
.4975124378109452736318407960199004975124

т.е. в принципе условные блоки что-то вытворяют с числами?

Обновленный db<>fiddle

Ответы

▲ 1Принят
SELECT
    SUM(
        CASE
            WHEN t.is_wo_nds = 1 THEN t.sum_wo_tax
            ELSE t.sum_w_tax
        END * t.excluded_from_delivery
    ) / NULLIF(SUM(
        CASE
            WHEN t.is_wo_nds = 1 THEN t.sum_wo_tax
            ELSE t.sum_w_tax
        END
    ), 0) AS coef
FROM (
    SELECT 0 AS is_wo_nds, 100 AS sum_wo_tax, 100 AS sum_w_tax, 1 AS excluded_from_delivery FROM dual
    UNION ALL
    SELECT 0 AS is_wo_nds, 100 AS sum_wo_tax, 100 AS sum_w_tax, 0 AS excluded_from_delivery FROM dual
    UNION ALL
    SELECT 0 AS is_wo_nds, 0 AS sum_wo_tax, 0 AS sum_w_tax, 1 AS excluded_from_delivery FROM dual
) t;

По комментариям пришли к этому варианту.

Шли от упрощения к усложнению.
Сначала переработали логику:
SUM(t.sum_w_tax * (1 - t.excluded_from_delivery)) / SUM(t.sum_w_tax)

Максимально упростили. 1- в последствии убрали (в комментариях).

Через CASE выражение выбираем соответствующую сумму в зависимости от значения определителя НДС is_wo_nds. Если is_wo_nds равен 1, берём sum_wo_tax, иначе — sum_w_tax.

Деление на ноль

Самое сильные хитрости были из-за предусмотра деления на ноль. Тут всё можно упростить через NULLIF(SUM(…), 0) для знаменателя в выражении деления. Если сумма заказа без учета исключенных позиций равна нулю, то результат деления будет NULL, и ошибку деления на ноль избежим.

▲ 1

Вот тут вот

sum(decode(t.is_wo_nds, 1, decode(t.sum_wo_tax,0,1,t.sum_wo_tax), decode(t.sum_w_tax,0,1,t.sum_w_tax))) as coef

зачем единицы стоят???

100/201=.4975124378109452736318407960199004975124