ALV

Display Minus Sign Before Value in ALV

This article demonstrates how to display negative values within the SAP ALV grid with a leading minus sign. For this purpose, a custom ABAP conversion exit routine is implemented, which properly respects various individual user parameters for decimal separators.

Test Requirements

  • Display negative numbers within the ALV with the minus sign placed before the value.
  • Retain the individual user-specific decimal notation configured within the SU01/SU3 settings.

Core Considerations and Strategy

  1. Can this be resolved utilizing standard ALV grid options?
    • No. Neither the field catalog settings nor the global ALV layout settings provide configuration parameters to adjust the sign positioning.
  2. Is custom ABAP development required?
    • Yes. The solution consists of a user-defined conversion exit routine, which is created as a function group containing two distinct function modules.

Implementation Steps

Note: Warnings and development package assignment dialogs have been deliberately omitted from this walkthrough. For demonstration purposes, all repository objects were created inside the local package $TMP.

Key Factors to Consider

When implementing a conversion exit routine for numeric data types, you must strictly respect the user-specific decimal notation defined within the SU3 settings. A static EDIT MASK pattern is insufficient here; instead, the locale-dependent formatting should be determined via native built-in ABAP string functions to guarantee correct output across diverse user profiles.

Creating the Conversion Exit Routine

A conversion exit routine is identified by a unique five-character suffix name and consists of a matched pair of function modules adhering to a strict framework naming convention:

  • CONVERSION_EXIT_xxxxx_INPUT - handling the transformation from the external format into the internal representation.
  • CONVERSION_EXIT_xxxxx_OUTPUT - handling the transformation from the internal representation into the external display format.

Creating the Function Group

The function group container was created via standard transaction SE37.
Enter the requested group name along with a descriptive short text and save your attributes.

Navigate to the Top Include section of your newly created function group and paste the following source code block:

Code: Show TOP Include (4 lines)
FUNCTION-POOL zstkoes_sign_conversion.

DATA gv_decimal_sign TYPE char1.
DATA gv_separator TYPE char1.

Function Module: Internal → Display (OUTPUT)

Specify the standardized module name CONVERSION_EXIT_XXXXX_OUTPUT on the initial screen.
Allocate the function module to the newly generated function group container.
Importing Parameter: INPUT
Exporting Parameter: OUTPUT
Code: Show OUTPUT EXIT (35 lines)
  " Get the Decimal Format from the user settings (SU01/SU3)
  " Dezimaldarstellung aus den Benutzereinstellungen holen (SU01/SU3)
  IF gv_decimal_sign IS INITIAL.
    CALL FUNCTION 'CLSE_SELECT_USR01'
      EXPORTING
        username         = sy-uname
        iv_delete_buffer = abap_true
      IMPORTING
        decimal_sign     = gv_decimal_sign
        separator        = gv_separator.
  ENDIF.
  DATA(lv_input) = CONV string( input ).
  DATA(lv_negativ_number) = xsdbool( lv_input CS '-' ).
  REPLACE ALL OCCURRENCES OF '-' IN lv_input WITH ''.
  SPLIT lv_input AT '.' INTO DATA(lv_number) DATA(lv_decimals).
  DO strlen( lv_number ) TIMES.
    IF sy-index EQ 1.
      DATA(lv_mask) = |_|.
      CONTINUE.
    ENDIF.
    lv_mask = |_{ COND #( WHEN sy-index MOD 3 EQ 1
                          THEN gv_separator ) }{
                  lv_mask }|.
  ENDDO.
  DO strlen( lv_decimals ) TIMES.
    IF sy-index EQ 1.
      lv_mask = |{ lv_mask }{ gv_decimal_sign }_|.
      CONTINUE.
    ENDIF.
    lv_mask = |{ lv_mask }_|.
  ENDDO.
  lv_mask = |{ COND #( WHEN lv_negativ_number EQ abap_true
                       THEN |-| ) }{
               lv_mask }|.
  WRITE input TO output USING EDIT MASK lv_mask.
Code: Show OUTPUT EXIT (Classic Syntax) (42 lines)
  DATA lv_input TYPE string.
  DATA lv_negativ_number TYPE abap_bool.
  DATA lv_number TYPE string.
  DATA lv_decimals TYPE string.
  DATA lv_mask TYPE string.

  " Get the Decimal Format from the user settings (SU01/SU3)
  " Dezimaldarstellung aus den Benutzereinstellungen holen (SU01/SU3)
  IF gv_decimal_sign IS INITIAL.
    CALL FUNCTION 'CLSE_SELECT_USR01'
      EXPORTING
        username         = sy-uname
        iv_delete_buffer = abap_true
      IMPORTING
        decimal_sign     = gv_decimal_sign
        separator        = gv_separator.
  ENDIF.
  lv_input = input.
  lv_negativ_number = boolc( lv_input CS '-' ).
  REPLACE ALL OCCURRENCES OF '-' IN lv_input WITH ''.
  SPLIT lv_input AT '.' INTO lv_number lv_decimals.
  DO strlen( lv_number ) TIMES.
    IF sy-index EQ 1.
      lv_mask = '_'.
      CONTINUE.
    ENDIF.
    IF sy-index MOD 3 EQ 1.
      CONCATENATE gv_separator lv_mask INTO lv_mask.
    ENDIF.
    CONCATENATE '_' lv_mask INTO lv_mask.
  ENDDO.
  DO strlen( lv_decimals ) TIMES.
    IF sy-index EQ 1.
      CONCATENATE lv_mask gv_decimal_sign '_' INTO lv_mask.
      CONTINUE.
    ENDIF.
      CONCATENATE lv_mask '_' INTO lv_mask.
  ENDDO.
  IF lv_negativ_number EQ abap_true.
    CONCATENATE '-' lv_mask INTO lv_mask.
  ENDIF.
  WRITE input TO output USING EDIT MASK lv_mask.

Function Module: Display → Internal (INPUT)

Utilize transaction SE37 to create the matching INPUT variant underneath the same exit routine identifier.
Allocate the processing logic block to your generated function group context.
Importing Parameter: INPUT
Exporting Parameter: OUTPUT
Exception Parameter: ERROR_MESSAGE

The ERROR_MESSAGE exception signature is mandatory for robust exception handling routines and should never be omitted.

Code: Show INPUT EXIT (30 lines)
  " Get the Decimal Format from the user settings (SU01/SU3)
  " Dezimaldarstellung aus den Benutzereinstellungen holen (SU01/SU3)
  IF gv_decimal_sign IS INITIAL.
    CALL FUNCTION 'CLSE_SELECT_USR01'
      EXPORTING
        username         = sy-uname
        iv_delete_buffer = abap_true
      IMPORTING
        decimal_sign     = gv_decimal_sign
        separator        = gv_separator.
  ENDIF.
  DATA(lv_input) = CONV string( input ).
  REPLACE ALL OCCURRENCES OF gv_separator IN lv_input WITH ''.
  REPLACE gv_decimal_sign IN lv_input WITH '.'.
  CONDENSE lv_input NO-GAPS.
  TRY.
      output = lv_input.
    CATCH cx_sy_conversion_no_number.
      " Clear message fields of sy structure,
      " so that 'Conversion error' will be raised
      " Alle Message Felder der sy Struktur löschen,
      " damit 'Fehler bei der Konvertierung.' geworfen wird
      CLEAR sy-msgty.
      CLEAR sy-msgno.
      CLEAR sy-msgv1.
      CLEAR sy-msgv2.
      CLEAR sy-msgv3.
      CLEAR sy-msgv4.
      RAISE error_message.
  ENDTRY.
Code: Show INPUT EXIT (Classic Syntax) (31 lines)
  DATA: lv_input TYPE string.
  " Get the Decimal Format from the user settings (SU01/SU3)
  " Dezimaldarstellung aus den Benutzereinstellungen holen (SU01/SU3)
  IF gv_decimal_sign IS INITIAL.
    CALL FUNCTION 'CLSE_SELECT_USR01'
      EXPORTING
        username         = sy-uname
        iv_delete_buffer = abap_true
      IMPORTING
        decimal_sign     = gv_decimal_sign
        separator        = gv_separator.
  ENDIF.
  lv_input = input.
  REPLACE ALL OCCURRENCES OF gv_separator IN lv_input WITH ''.
  REPLACE gv_decimal_sign IN lv_input WITH '.'.
  CONDENSE lv_input NO-GAPS.
  TRY.
      output = lv_input.
    CATCH cx_sy_conversion_no_number.
      " Clear message fields of sy structure,
      " so that 'Conversion error' will be raised
      " Alle Message Felder der sy Struktur löschen,
      " damit 'Fehler bei der Konvertierung.' geworfen wird
      CLEAR  sy-msgty.
      CLEAR  sy-msgno.
      CLEAR  sy-msgv1.
      CLEAR  sy-msgv2.
      CLEAR  sy-msgv3.
      CLEAR  sy-msgv4.
      RAISE error_message.
  ENDTRY.

Activation Sequence

Conclusively, activate the function group container along with both underlying function modules.

Deploying the Conversion Exit Routine

There are two strategic approaches to apply the newly created conversion routine within your application architecture:

Assigning the Conversion Exit to a Domain Structure:

Create a new domain or copy an existing dictionary template and allocate the conversion exit routine to the domain attributes. Any data elements based on this domain will automatically inherit and execute the routine — including inside ALV grid rows.

Assigning the Conversion Exit Dynamically at ALV Runtime:

The demo report demonstrates how to dynamically allocate the routine to specific fields of the ALV grid by modifying the field catalog matrix at runtime.

Demo Report Walkthrough

The following ABAP report illustrates the dynamic assignment of the conversion exit routine to specific ALV column nodes at runtime.

Code: Show Demo Report (78 lines)
REPORT zstkoes_alv_sort.
  TYPES:
    BEGIN OF ts_mard,
      matnr   TYPE matnr,
      werks   TYPE werks_d,
      lgort   TYPE lgort_d,
      labst   TYPE labst,
      zlabst  TYPE zlabst,
      h_level TYPE h_level,
    END OF ts_mard.
  TYPES tt_mard TYPE STANDARD TABLE OF ts_mard
                WITH EMPTY KEY.

  START-OF-SELECTION.
    DATA(gt_mard) = VALUE tt_mard( ( matnr = CONV numc4( 1 )
                                     werks = '0001
                                     lgort = '0001'
                                     labst = '-9.999'
                                     zlabst = '-9.999'
                                     h_level = -99999999 )
                                   ( matnr = CONV numc4( 1 )
                                     werks = '0001'
                                     lgort = '0002'
                                     labst = '-99.999'
                                     zlabst = '-99.999'
                                     h_level = -9999999 )
                                   ( matnr = CONV numc4( 1 )
                                     werks = '0002'
                                     lgort = '0001'
                                     labst = '-999.999'
                                     zlabst = '-999.999'
                                     h_level = -999999 )
                                   ( matnr = CONV numc4( 1 )
                                     werks = '0002'
                                     lgort = '0002'
                                     labst = '-9999.999'
                                     zlabst = '-9999.999'
                                     h_level = -99999 )
                                   ( matnr = CONV numc4( 2 )
                                     werks = '0001'
                                     lgort = '0001'
                                     labst = '-99999.999'
                                     zlabst = '-99999.999'
                                     h_level = -9999 )
                                   ( matnr = CONV numc4( 2 )
                                     werks = '0001'
                                     lgort = '0002'
                                     labst = '-999999.999'
                                     zlabst = '-999999.999'
                                     h_level = -999 )
                                   ( matnr = CONV numc4( 2 )
                                     werks = '0002'
                                     lgort = '0001'
                                     labst = '-9999999.999'
                                     zlabst = '-9999999.999'
                                     h_level = -99 )
                                   ( matnr = CONV numc4( 2 )
                                     werks = '0002'
                                     lgort = '0002'
                                     labst = '-99999999.999'
                                     zlabst = '-99999999.999'
                                     h_level = -9 ) ).
  
  END-OF-SELECTION.
    cl_salv_table=>factory(
      IMPORTING
        r_salv_table = DATA(lo_table)
      CHANGING
        t_table      = gt_mard ).
    lo_table->get_functions( )->set_all( ).
    DATA(it_column_ref) = lo_table->get_columns( )->get( ).
    LOOP AT it_column_ref ASSIGNING FIELD-SYMBOL(<st_column_ref>).
      CASE <st_column_ref>-columnname.
        WHEN 'H_LEVEL'.
          <st_column_ref>-r_column->set_edit_mask( value = '==ZSIGN' ).
      ENDCASE.
    ENDLOOP.
    lo_table->display( ).
Code: Show Demo Report (Classic Syntax) (93 lines)
REPORT zstkoes_alv_sort.
  TYPES:
    BEGIN OF ts_mard,
      matnr   TYPE matnr,
      werks   TYPE werks_d,
      lgort   TYPE lgort_d,
      labst   TYPE labst,
      zlabst  TYPE zlabst,
      h_level TYPE h_level,
    END OF ts_mard,
  TYPES tt_mard TYPE STANDARD TABLE OF ts_mard.

  DATA gt_mard TYPE tt_mard.
  DATA gt_column_ref TYPE salv_t_column_ref.

  DATA go_salv TYPE REF TO cl_salv_table.

  FIELD-SYMBOLS <gs_mard> TYPE ts_mard.
  FIELD-SYMBOLS <gs_column_ref> TYPE salv_s_column_ref.
    
  START-OF-SELECTION.
    APPEND INITIAL LINE TO gt_mard ASSIGNING <gs_mard>.
    <gs_mard>-matnr = '0001'.
    <gs_mard>-werks = '0001'.
    <gs_mard>-lgort = '0001'.
    <gs_mard>-labst = '-9.999'.
    <gs_mard>-zlabst = '-9.999'.
    <gs_mard>-h_level = -99999999.
    APPEND INITIAL LINE TO gt_mard ASSIGNING <gs_mard>.
    <gs_mard>-matnr = '0001'.
    <gs_mard>-werks = '0001'.
    <gs_mard>-lgort = '0002'.
    <gs_mard>-labst = '-99.999'.
    <gs_mard>-zlabst = '-99.999'.
    <gs_mard>-h_level = -9999999.
    APPEND INITIAL LINE TO gt_mard ASSIGNING <gs_mard>.
    <gs_mard>-matnr = '0001'.
    <gs_mard>-werks = '0002'.
    <gs_mard>-lgort = '0001'.
    <gs_mard>-labst = '-999.999'.
    <gs_mard>-zlabst = '-999.999'.
    <gs_mard>-h_level = -999999.
    APPEND INITIAL LINE TO gt_mard ASSIGNING <gs_mard>.
    <gs_mard>-matnr = '0001'.
    <gs_mard>-werks = '0002'.
    <gs_mard>-lgort = '0002'.
    <gs_mard>-labst = '-9999.999'.
    <gs_mard>-zlabst = '-9999.999'.
    <gs_mard>-h_level = -99999.
    APPEND INITIAL LINE TO gt_mard ASSIGNING <gs_mard>.
    <gs_mard>-matnr = '0002'.
    <gs_mard>-werks = '0001'.
    <gs_mard>-lgort = '0001'.
    <gs_mard>-labst = '-99999.999'.
    <gs_mard>-zlabst = '-99999.999'.
    <gs_mard>-h_level = -9999.
    APPEND INITIAL LINE TO gt_mard ASSIGNING <gs_mard>.
    <gs_mard>-matnr = '0002'.
    <gs_mard>-werks = '0001'.
    <gs_mard>-lgort = '0002'.
    <gs_mard>-labst = '-999999.999'.
    <gs_mard>-zlabst = '-999999.999'.
    <gs_mard>-h_level = -999.
    APPEND INITIAL LINE TO gt_mard ASSIGNING <gs_mard>.
    <gs_mard>-matnr = '0002'.
    <gs_mard>-werks = '0002'.
    <gs_mard>-lgort = '0001'.
    <gs_mard>-labst = '-9999999.999'.
    <gs_mard>-zlabst = '-9999999.999'.
    <gs_mard>-h_level = -99.
    APPEND INITIAL LINE TO gt_mard ASSIGNING <gs_mard>.
    <gs_mard>-matnr = '0002'.
    <gs_mard>-werks = '0002'.
    <gs_mard>-lgort = '0002'.
    <gs_mard>-labst = '-99999999.999'.
    <gs_mard>-zlabst = '-99999999.999'.
    <gs_mard>-h_level = -9.
  
  END-OF-SELECTION.
    cl_salv_table=>factory(
      IMPORTING
        r_salv_table = go_salv
      CHANGING
        t_table      = gt_mard ).
    go_salv->get_functions( )->set_all( ).
    gt_column_ref = go_salv->get_columns( )->get( ).
    LOOP AT gt_column_ref ASSIGNING <gs_column_ref>.
      CASE <gs_column_ref>-columnname.
        WHEN 'H_LEVEL'.
          <gs_column_ref>-r_column->set_edit_mask( value = '==ZSIGN' ).
      ENDCASE.
    ENDLOOP.
    go_salv->display( ).

Runtime Results Comparison

The final execution grid clearly visualizes the behavioral differences between the columns:

  • The fourth column „Frei verwendbar" configured without a conversion routine displays the minus sign trailing behind the numeric value according to ALV standard formatting.
  • The fifth column „Frei verwendbar" utilizing the custom conversion routine displays the minus sign leading before the numeric sequence exactly as requested — while seamlessly preserving user decimal formats.
  • The sixth column „Baugruppenstufe" maps an INTEGER field type, demonstrating that the conversion logic performs equally well for whole numbers.

Back to Blog Overview