Меню

REDUCE: не помнИ типизацию, а пОмни про нее

|

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

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

Что такое REDUCE?

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

Частым примером для использования REDUCE является вычисление суммы в таблице. Давайте рассмотрим демо-задачу.

Демо-задача для подсчета промежуточных итогов

Задача: посчитать сумму по позициям заказа. Исходная структура: продукт, сумма, валюта.

Давайте рассмотрим несколько вариантов решений на ABAP/4.

Решение без оператора REDUCE:

TYPES: BEGIN OF ts_order_line,
             matnr TYPE matnr,
             netwr TYPE netwr,
             waerk TYPE waerk,
           END OF ts_order_line,
           tt_order_line TYPE STANDARD TABLE OF ts_order_line WITH DEFAULT KEY
           .
    DATA lt_order_line TYPE tt_order_line.
    DATA ls_order_line TYPE ts_order_line.

    DATA lv_total_amount TYPE netwr.

    ls_order_line-matnr = 'MATNR_01'.
    ls_order_line-netwr = '100.50'.
    ls_order_line-waerk = 'RUB'.
    APPEND ls_order_line TO lt_order_line.

    ls_order_line-matnr = 'MATNR_02'.
    ls_order_line-netwr = '110.50'.
    ls_order_line-waerk = 'RUB'.
    APPEND ls_order_line TO lt_order_line.

    ls_order_line-matnr = 'MATNR_03'.
    ls_order_line-netwr = '120.50'.
    ls_order_line-waerk = 'RUB'.
    APPEND ls_order_line TO lt_order_line.

    ls_order_line-matnr = 'MATNR_04'.
    ls_order_line-netwr = '130.50'.
    ls_order_line-waerk = 'RUB'.
    APPEND ls_order_line TO lt_order_line.

    CLEAR lv_total_amount.
    LOOP AT lt_order_line INTO ls_order_line.
      lv_total_amount = lv_total_amount + ls_order_line-netwr.
    ENDLOOP.

    BREAK-POINT.

Код-Листинг 1. Сумма в массиве через LOOP (без оператора REDUCE).

Как результат получаем следующее: старый добрый LOOP отработал, пройдя по всем строкам в таблице, и итоговая сумма ушла в переменную LV_TOTAL_AMOUNT (прообраз аргумента-аккумулятора). На экране ниже показано значение в отладчике (Рис.1.).

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

Теперь давайте решим ту же задачу, но через функцию REDUCE.

С оператором REDUCE – три возможных решения, в том числе и неправильное ????

    TYPES: BEGIN OF ts_order_line,
             matnr TYPE matnr,
             netwr TYPE netwr,
             waerk TYPE waerk,
           END OF ts_order_line,
           tt_order_line TYPE STANDARD TABLE OF ts_order_line WITH DEFAULT KEY
           .
    DATA lt_order_line TYPE tt_order_line.
    DATA lv_total_amount TYPE netwr.

    lt_order_line = VALUE #(
    ( matnr = 'MATNR_01' netwr

Если хотите прочитать статью полностью и оставить свои комментарии присоединяйтесь к sapland

У вас уже есть учетная запись?

Войти

Обсуждения Количество комментариев4

Комментарий от  

Виталий Глущенко

  |  31 марта 2022, 20:09

я бы написал вот так
lv_total_amount = reduce #(
    init _r4_tot_am = value #( )
    for _ord_line4 in lt_order_line
    next _r4_tot_am = _r4_tot_am + _ord_line4-netwr
).
break-point.
 
потому что если в определении переменой lv_total_amount поменять тип на i, то тест 1 начинает работать правильно, а 2 и 3 считают неправильно, а если меняем тип на f, то все 3 варианта не работают. В данном же коде тип определяется на основании типа lv_total_amount.

Комментарий от  

Олег Башкатов

  |  31 марта 2022, 20:57

я бы написал вот так
lv_total_amount = reduce #(
    init _r4_tot_am = value #( )
    for _ord_line4 in lt_order_line
    next _r4_tot_am = _r4_tot_am + _ord_line4-netwr
).
break-point.
 
потому что если в определении переменой lv_total_amount поменять тип на i, то тест 1 начинает работать правильно, а 2 и 3 считают неправильно, а если меняем тип на f, то все 3 варианта не работают. В данном же коде тип определяется на основании типа lv_total_amount.

Спасибо!
Ваш подход классный! и здорово дополняет копилку тонкостей reduce )
 
>>> lv_total_amount поменять тип на i, то тест 1 начинает работать правильно
нет. получаемый ответ будет 604, а точный ответ 602.25.
PS. Слово "правильный" я бы заменил на точный/неточный. если требуемая точность - до сотен, то все варианты "правильные".
 
>>> а 2 и 3 считают неправильно, а если меняем тип на f
нет. подсчет идет точный, но результат 6.0225000000000000E+02 уж очень странный для переменной total_amount )))
а что подразумеваете под "2 и 3 считают неправильно"?

Комментарий от  

Виталий Глущенко

  |  03 апреля 2022, 21:13

Спасибо!
Ваш подход классный! и здорово дополняет копилку тонкостей reduce )
 
>>> lv_total_amount поменять тип на i, то тест 1 начинает работать правильно
нет. получаемый ответ будет 604, а точный ответ 602.25.
PS. Слово "правильный" я бы заменил на точный/неточный. если требуемая точность - до сотен, то все варианты "правильные".
 
>>> а 2 и 3 считают неправильно, а если меняем тип на f
нет. подсчет идет точный, но результат 6.0225000000000000E+02 уж очень странный для переменной total_amount )))
а что подразумеваете под "2 и 3 считают неправильно"?

>>нет. получаемый ответ будет 604, а точный ответ 602.25.
>>PS. Слово "правильный" я бы заменил на точный/неточный. если требуемая точность - до сотен, то все варианты "правильные".
 
да, правильный/неправильный - плохая формулировка, но и точный/неточный тоже не хочу называть, потому что иногда в задаче требуется получить неточный результат, но он правильный по требованиям задачи.
 
Поясню, что я имел ввиду выше. В описанных примерах рассматривается ситуация когда из-за незнания как работает reduce можно получить несколько конвертаций и на каждой из них потерять точность сильнее, чем это ожидалось.
Грубо говоря конвертации у нас происходят:
  1. в строке next к типу переменной _rX_tot_am приводится тип переменной _ord_lineX-netwr;
  2. на выходе из reduce результат расчета в переменной _rX_tot_am приводится к типу указанному после reduce ...( );
  3. при присвоении переменной lv_total_amount результат шага 2 приводится к типу переменной lv_total_amount;
Предположим, что тип _ord_lineX-netwr и тип lv_total_amount у меня указан правильно(он может быть одинаковым, а может быть разным). Обычно моя задача выполнить все калькуляции и конвертацию с максимальной точностью. В таком случае хорошо, когда на шагах 1 и 2 и 3 все операции выполняются с одиним и тем же типом и лучше всего, когда это тип переменной lv_total_amount. Это важно потому что делает конструкцию менее капризной к смене типов переменных и таким образом избежать "детских" ошибок при модификации кода в будущем, когда в одном месте тип поменяли, а в другом забыли.
 
Иногда бывает, что нужно выполнять вычисления с большей точностью, чем мы потом будем хранить результат, в там случае в конструкции
    init _r4_tot_am = value #( )
я бы заменил # на явно заданный тип большей точности, но только там, а остальное оставил бы как есть, по той же самой причине, что бы конвертаций было как можно меньше.
 
>>нет. подсчет идет точный, но результат 6.0225000000000000E+02 уж очень странный для переменной total_amount )))
Почему? вполне нормальный результат, типичная экспоненциальная запись. В SAP в целом не часто используется тип float, но если в рамках задачи это требуется, то почему нет, в точности мы тут не потеряли, хотя могли и существенно.
 
>>а что подразумеваете под "2 и 3 считают неправильно"?
развернуто ответил выше, если кратко неправильным считаю, то что при смене типа его приходится менять в 3-х местах, что в будущем может привести к ошибкам.

Комментарий от  

Олег Башкатов

  |  03 апреля 2022, 23:07

>>нет. получаемый ответ будет 604, а точный ответ 602.25.
>>PS. Слово "правильный" я бы заменил на точный/неточный. если требуемая точность - до сотен, то все варианты "правильные".
 
да, правильный/неправильный - плохая формулировка, но и точный/неточный тоже не хочу называть, потому что иногда в задаче требуется получить неточный результат, но он правильный по требованиям задачи.
 
Поясню, что я имел ввиду выше. В описанных примерах рассматривается ситуация когда из-за незнания как работает reduce можно получить несколько конвертаций и на каждой из них потерять точность сильнее, чем это ожидалось.
Грубо говоря конвертации у нас происходят:
  1. в строке next к типу переменной _rX_tot_am приводится тип переменной _ord_lineX-netwr;
  2. на выходе из reduce результат расчета в переменной _rX_tot_am приводится к типу указанному после reduce ...( );
  3. при присвоении переменной lv_total_amount результат шага 2 приводится к типу переменной lv_total_amount;
Предположим, что тип _ord_lineX-netwr и тип lv_total_amount у меня указан правильно(он может быть одинаковым, а может быть разным). Обычно моя задача выполнить все калькуляции и конвертацию с максимальной точностью. В таком случае хорошо, когда на шагах 1 и 2 и 3 все операции выполняются с одиним и тем же типом и лучше всего, когда это тип переменной lv_total_amount. Это важно потому что делает конструкцию менее капризной к смене типов переменных и таким образом избежать "детских" ошибок при модификации кода в будущем, когда в одном месте тип поменяли, а в другом забыли.
 
Иногда бывает, что нужно выполнять вычисления с большей точностью, чем мы потом будем хранить результат, в там случае в конструкции
    init _r4_tot_am = value #( )
я бы заменил # на явно заданный тип большей точности, но только там, а остальное оставил бы как есть, по той же самой причине, что бы конвертаций было как можно меньше.
 
>>нет. подсчет идет точный, но результат 6.0225000000000000E+02 уж очень странный для переменной total_amount )))
Почему? вполне нормальный результат, типичная экспоненциальная запись. В SAP в целом не часто используется тип float, но если в рамках задачи это требуется, то почему нет, в точности мы тут не потеряли, хотя могли и существенно.
 
>>а что подразумеваете под "2 и 3 считают неправильно"?
развернуто ответил выше, если кратко неправильным считаю, то что при смене типа его приходится менять в 3-х местах, что в будущем может привести к ошибкам.

Во 1ых, спасибо за развернутый комментарий.
 
Но в одном месте - я не совсем понял его.
Вы говорите, как я Вас понял (и я с Вами полностью согласен), что калькуляцию нужно выполнить с максимальной точностью, а вот дать результат в том формате, который запрашивается.
Но ведь, если мы будем зависеть от исходной переменной lv_total_amount, то мы как раз-таки и рискуем "понизить" нашу точность вычислений. и вот целочисленное решение задачи в статье должно быть 602, а не 604. Потому что точное 602.25  и из него int = 602, а если мы "унаследуем неявно" целочисленный тип из lv_total_amount, то получим 604.
 
Вы какой подход поддерживаете:
1) неявное наследование типа из запрашиваемой переменной
2) явное указание типизации при вычислениях
3) возвращаемый тип из reduce делаем наследуемым (после ключевого слова reduce берем из целевого результата через #), а внутри повышаем точность до разумного уровня явной типизацией.