Maximum positive value that can be stored by integer type variable in ABAP is 2147483647.
For values beyond maximum limit of integer variable, workarounds can be used.
One such example is discussion MOD operation fail for large numbers started by Starlet Abraham
I was able to calculate correct result of 512^17 MOD 2773 using the methods detailed below.
I have written this program to do following operations on large integers.
- Addition
- Subtraction
- Multiplication
- Division
- Mod
- Power
Full code snippet is posted at the end of this document.
Program has its flaws, but it works.
I would briefly go through how the logic works.
How are big numbers stored?
Local class lcl_bignum has instance attributes that store big numbers.
LV_STRING - Number stored as string type.
LT_INT - an internal table of integers. The string is broken into substrings of size 4 (base 10000), and those substrings ( as int4 ) are stored in internal table.
LV_SIGN - integer type to store the sign of number. +1 for positive, -1 for negative.
For example, number 1234567890 will be stored in internal table as
LT_INT |
---|
7890 |
3456 |
12 |
Why is number broken into base 10000?
Base 10000 is safe considering the capacity of integer variable type in ABAP.
Computing 9999 * 9999 results in 99980001, which is within the limits of integer type.
On the other hand, 99999 * 99999 results in 999980001, which would lead to overflow exception.
How to create instance and store big number?
Below code snippets can be used to create instance of big number.
The constructor parameters are options.
CREATE OBJECT lr3.
--
Above code will create instance with zero value in it.
CREATE OBJECT lr2
EXPORTING
i_value = '533335555511111'.
--
Above code will create instance and populate lv_string and lt_int by breaking down input string.
Sign of number is positive by default.
Input string should have only numbers in it.
CREATE OBJECT lr1
EXPORTING
i_value = '6584389034291301828999'
i_negative = 'X'.
--
Using above code, 'X' can be passed i_negative flag if we have a negative number.
How to add numbers?
Instance method Add( ) can be used for adding numbers.
It accepts 2 object reference that need to be added, and result is stored in object whose method gets called.
We are adding the numbers starting lowest weightage (LT_INT[1]), and carrying forward extra in case of overflow.
For example, we add 12345678 and 22223333. Program would add (5678,3333) first, and then (1234,2222).
For A = B + C, below code can be used.
lr3->add( i_obj1 = lr1
i_obj2 = lr2 ).
--
For A = A + B, below code can be used.
lr3->add( i_obj1 = lr3
i_obj2 = lr2 ).
--
How to subtract numbers?
Instance method Subtract( ) can be used for subtracting numbers.
The usage is same as Add( ) method.
Looking at code, you would see that Add( ) and Subtract( ) are calling each other.
It is done the tackle how absolute values are treated depending on their sign.
For example, A = B + C, the values of B and C will be added, and common sign will be retained if B and C have same sign. Both positive, or both negative.
If the signs of B and C are different, we essentially have to keep sign of B, and calculate |B| - |C| (absolute values).
How to multiply numbers?
Instance method Multiply( ) can be used.
It accepts 2 object references, and stores result in calling object.
I would explain the logic using example. It is similar to the method used in elementary school.
For elementary example of multiplying 123 and 456, below logic is used.
123
456
===
3*6 + (3*5+2*6) + (3*4+2*5+1*6) + (2*4+1*5) + 1*4
Same thing is done in program at base 10000 level.
At every stage we are carrying forward the overflow value.
How to divide numbers?
For division, I am guessing the answer by multiplying.
Something like binary search, I start with 1 as answer, keep on multiplying it by 10000 still answer crosses dividend.
Once the guess surpasses dividend, I take a step back, and start multiplying by square root of 10000.
A point comes when multiplier is reduced to 1, and multiplication can no longer be used to get closer to answer.
I then use addition to get closer to answer. Instead of square root, divide by 2 to get next "jump value".
A dry run using elementary example would be something like this.
451
4
---
How to do calculate remainder (MOD operation)
Instance method Mod( ) can be used, which internally uses Divide( ), Multiply( ) and Subtract( ).
To calculate A = B MOD C, program does something like:
A = B - C * ( B DIV C )
Here we are reusing the code written in previous methods.
How to calculate Power?
Instance method Power( ) can be used, which internally uses Multiply( ) and Subtract( ).
For calculating A = B POWER C, program starts with 1 as answer, uses loop to multiply B to answer, C number of times.
Full code snippet
- *----------------------------------------------------------------------*
- * CLASS lcl_bignum DEFINITION
- *----------------------------------------------------------------------*
- *
- *----------------------------------------------------------------------*
- CLASS lcl_bignum DEFINITION.
- PUBLICSECTION.
- METHODS constructor IMPORTING i_value TYPE string OPTIONAL
- i_negative TYPEcOPTIONAL.
- METHODS get_sign RETURNING value(r_sign) TYPEi.
- METHODS get_string RETURNING value(r_string) TYPE string.
- METHODS get_int RETURNING value(r_int) TYPE int4_table.
- METHODS set_data IMPORTING i_value TYPE string
- i_negative TYPEcOPTIONAL.
- METHODSaddIMPORTING i_obj1 TYPEREFTO lcl_bignum
- i_obj2 TYPEREFTO lcl_bignum.
- METHODSsubtractIMPORTING i_obj1 TYPEREFTO lcl_bignum
- i_obj2 TYPEREFTO lcl_bignum.
- METHODSmultiplyIMPORTING i_obj1 TYPEREFTO lcl_bignum
- i_obj2 TYPEREFTO lcl_bignum.
- METHODSdivideIMPORTING i_obj1 TYPEREFTO lcl_bignum
- i_obj2 TYPEREFTO lcl_bignum.
- METHODSmodIMPORTING i_obj1 TYPEREFTO lcl_bignum
- i_obj2 TYPEREFTO lcl_bignum.
- METHODS power IMPORTING i_obj1 TYPEREFTO lcl_bignum
- i_obj2 TYPEREFTO lcl_bignum.
- METHODS invert_sign.
- METHODS copy_values IMPORTING i_obj TYPEREFTO lcl_bignum.
- METHODS abs_compare IMPORTING i_obj TYPEREFTO lcl_bignum
- RETURNING value(r_result) TYPEi.
- PROTECTEDSECTION.
- METHODS int_to_string.
- METHODS split_into_int.
- METHODS delete_leading_zeros.
- METHODS divide_by_2.
- PRIVATESECTION.
- DATA lv_string TYPE string.
- DATA lv_sign TYPEiVALUE1.
- DATA lt_int TYPE int4_table.
- "sqrt of max int4 is 46340
- CONSTANTS: gc_unit_size TYPEiVALUE10000,
- gc_unit_digits TYPEiVALUE4,
- gc_gt TYPEiVALUE1, "greater than
- gc_lt TYPEiVALUE2, "less than
- gc_eq TYPEiVALUE0. "equals
- ENDCLASS. "lcl_bignum DEFINITION
- *----------------------------------------------------------------------*
- * CLASS lcl_bignum IMPLEMENTATION
- *----------------------------------------------------------------------*
- *
- *----------------------------------------------------------------------*
- CLASS lcl_bignum IMPLEMENTATION.
- METHOD constructor.
- IF i_value ISSUPPLIED.
- IF i_value CO' 1234567890'.
- lv_string = i_value.
- CONDENSE lv_string.
- ENDIF.
- split_into_int( ).
- ENDIF.
- IF i_negative ISSUPPLIED.
- IF i_negative EQ'X'.
- lv_sign = -1.
- ENDIF.
- ENDIF.
- ENDMETHOD. "constructor
- METHOD set_data.
- CLEAR: lv_string, lt_int.
- lv_sign = 1.
- IF i_value CO' 1234567890'.
- lv_string = i_value.
- CONDENSE lv_string.
- ENDIF.
- split_into_int( ).
- IF i_negative ISSUPPLIED.
- IF i_negative EQ'X'.
- lv_sign = -1.
- ENDIF.
- ENDIF.
- ENDMETHOD. "set_data
- METHOD split_into_int.
- DATA lv_string TYPE string.
- DATA lv_int TYPEi.
- lv_string = me->lv_string.
- WHILE lv_string ISNOTINITIAL.
- IFSTRLEN( lv_string ) GT gc_unit_digits.
- SHIFT lv_string RIGHT BY gc_unit_digits PLACES CIRCULAR.
- lv_int = lv_string+0(gc_unit_digits).
- SHIFT lv_string LEFTBY gc_unit_digits PLACES.
- ELSE.
- lv_int = lv_string.
- CLEAR lv_string.
- ENDIF.
- * INSERT lv_int INTO lt_int INDEX 1.
- APPEND lv_int TO lt_int.
- ENDWHILE.
- ENDMETHOD. "split_into_int
- METHOD delete_leading_zeros.
- DATA: lt_temp TYPE int4_table,
- lv_temp TYPEi,
- lv_count TYPEi.
- CHECK lt_int ISNOTINITIAL.
- lt_temp = lt_int.
- CLEAR lt_int.
- lv_count = LINES( lt_temp ).
- DO lv_count TIMES.
- READTABLE lt_temp INTO lv_temp INDEX lv_count.
- IF lv_temp ISNOTINITIAL.
- EXIT.
- ENDIF.
- lv_count = lv_count - 1.
- ENDDO.
- IF lv_count ISNOTINITIALAND
- lv_count LELINES( lt_temp ).
- APPENDLINESOF lt_temp FROM1TO lv_count TO lt_int.
- ENDIF.
- ENDMETHOD. "delete_leading_zeros
- METHOD int_to_string.
- DATA lv_int TYPEi.
- DATA lv_char TYPE n LENGTH gc_unit_digits.
- LOOPAT lt_int INTO lv_int.
- lv_char = lv_int.
- CONCATENATE lv_char lv_string INTO lv_string.
- ENDLOOP.
- SHIFT lv_string LEFT DELETING LEADING'0'.
- ENDMETHOD. "int_to_string
- METHOD get_int.
- r_int = lt_int.
- ENDMETHOD. "get_int
- METHOD get_sign.
- r_sign = lv_sign.
- ENDMETHOD. "get_sign
- METHOD get_string.
- r_string = lv_string.
- ENDMETHOD. "get_string
- METHOD copy_values.
- lv_string = i_obj->get_string( ).
- lv_sign = i_obj->get_sign( ).
- lt_int = i_obj->get_int( ).
- ENDMETHOD. "copy_values
- METHOD abs_compare.
- DATA: lt_int2 TYPE int4_table,
- lv_int TYPEi,
- lv_int2 TYPEi,
- lv_max TYPEi.
- lt_int2 = i_obj->get_int( ).
- lv_max = LINES( lt_int ).
- IFLINES( lt_int2 ) GT lv_max.
- lv_max = LINES( lt_int2 ).
- ENDIF.
- WHILE lv_max GT0.
- CLEAR: lv_int,
- lv_int2.
- READTABLE lt_int INTO lv_int INDEX lv_max.
- READTABLE lt_int2 INTO lv_int2 INDEX lv_max.
- IF lv_int EQ lv_int2.
- lv_max = lv_max - 1.
- ELSE.
- IF lv_int GT lv_int2.
- r_result = gc_gt.
- ELSEIF lv_int LT lv_int2.
- r_result = gc_lt.
- ENDIF.
- EXIT.
- ENDIF.
- ENDWHILE.
- ENDMETHOD. "abs_compare
- METHOD divide_by_2.
- DATA: lv_int TYPEi,
- lt_temp TYPE int4_table,
- lv_mod TYPEi,
- lv_count TYPEi.
- lv_count = LINES( lt_int ).
- DO lv_count TIMES.
- READTABLE lt_int INTO lv_int INDEX lv_count.
- IF sy-subrc EQ0.
- lv_int = lv_int + lv_mod * gc_unit_size.
- lv_mod = lv_int MOD2.
- lv_int = lv_int DIV2.
- INSERT lv_int INTO lt_temp INDEX1.
- ENDIF.
- lv_count = lv_count - 1.
- ENDDO.
- lt_int = lt_temp.
- delete_leading_zeros( ).
- int_to_string( ).
- ENDMETHOD. "divide_by_2
- METHOD invert_sign.
- lv_sign = lv_sign * -1.
- ENDMETHOD. "invert_sign
- METHODadd.
- DATA: lt_int1 TYPE int4_table,
- lt_int2 TYPE int4_table,
- lv_int TYPEi,
- lv_int1 TYPEi,
- lv_int2 TYPEi,
- lv_index TYPEi,
- lv_count TYPEi,
- lv_extra TYPEi.
- lt_int1 = i_obj1->get_int( ).
- lt_int2 = i_obj2->get_int( ).
- IF i_obj1->get_sign( ) NE i_obj2->get_sign( ).
- subtract( i_obj1 = i_obj1
- i_obj2 = i_obj2 ).
- EXIT.
- ENDIF.
- lv_count = LINES( lt_int1 ).
- IF lv_count LTLINES( lt_int2 ).
- lv_count = LINES( lt_int2 ).
- ENDIF.
- CLEAR: lt_int, lv_string.
- lv_sign = i_obj1->get_sign( ).
- DO lv_count TIMES.
- CLEAR: lv_int1,
- lv_int2.
- lv_index = sy-index.
- READTABLE lt_int1 INTO lv_int1 INDEX lv_index.
- READTABLE lt_int2 INTO lv_int2 INDEX lv_index.
- lv_int = lv_extra + lv_int1 + lv_int2.
- lv_extra = lv_int DIV gc_unit_size.
- lv_int = lv_int MOD gc_unit_size.
- APPEND lv_int TO lt_int.
- ENDDO.
- int_to_string( ).
- ENDMETHOD. "add
- METHODsubtract.
- DATA: lt_int1 TYPE int4_table,
- lt_int2 TYPE int4_table,
- lv_int TYPEi,
- lv_int1 TYPEi,
- lv_int2 TYPEi,
- lv_index TYPEi,
- lv_count TYPEi,
- lv_extra TYPEi.
- FIELD-SYMBOLS: <fs_int> TYPEi.
- lt_int1 = i_obj1->get_int( ).
- lt_int2 = i_obj2->get_int( ).
- IF i_obj1->get_sign( ) NE i_obj2->get_sign( ).
- i_obj2->invert_sign( ).
- add( i_obj1 = i_obj1
- i_obj2 = i_obj2 ).
- EXIT.
- ENDIF.
- lv_count = LINES( lt_int1 ).
- IF lv_count LTLINES( lt_int2 ).
- lv_count = LINES( lt_int2 ).
- ENDIF.
- CLEAR: lt_int, lv_string.
- lv_sign = i_obj1->get_sign( ).
- DO lv_count TIMES.
- CLEAR: lv_int1,
- lv_int2.
- lv_index = sy-index.
- READTABLE lt_int1 INTO lv_int1 INDEX lv_index.
- READTABLE lt_int2 INTO lv_int2 INDEX lv_index.
- lv_int = lv_extra + lv_int1 - lv_int2.
- CLEAR lv_extra.
- IF lv_int LT0.
- lv_int = lv_int + gc_unit_size.
- lv_extra = -1.
- ENDIF.
- lv_int = lv_int MOD gc_unit_size.
- APPEND lv_int TO lt_int.
- ENDDO.
- IF lv_extra ISNOTINITIAL.
- lv_sign = lv_sign * lv_extra.
- LOOPAT lt_int ASSIGNING<fs_int>.
- IF sy-tabix EQ1.
- <fs_int> = gc_unit_size - <fs_int>.
- ELSE.
- <fs_int> = gc_unit_size - <fs_int> + 1.
- ENDIF.
- ENDLOOP.
- ENDIF.
- delete_leading_zeros( ).
- int_to_string( ).
- ENDMETHOD. "subtract
- METHODmultiply.
- DATA: lt_int1 TYPE int4_table,
- lt_int2 TYPE int4_table,
- lv_int TYPEi,
- lv_int1 TYPEi,
- lv_int2 TYPEi,
- lv_int_tmp TYPEi,
- lv_index TYPEi,
- lv_index_start TYPEi,
- lv_index_end TYPEi,
- lv_index_start_tmp TYPEi,
- lv_index_end_tmp TYPEi,
- lv_count TYPEi,
- lv_carry TYPEi,
- lv_extra TYPEi.
- lt_int1 = i_obj1->get_int( ).
- lt_int2 = i_obj2->get_int( ).
- lv_count = LINES( lt_int1 ).
- IF lv_count LTLINES( lt_int2 ).
- lv_count = LINES( lt_int2 ).
- ENDIF.
- CLEAR: lt_int, lv_string.
- lv_sign = i_obj1->get_sign( ) * i_obj2->get_sign( ).
- CHECK lv_count ISNOTINITIAL.
- lv_index_start = 1.
- lv_index_end = 1.
- DO.
- CLEAR lv_int.
- lv_index_start_tmp = lv_index_start.
- lv_index_end_tmp = lv_index_end.
- lv_int = lv_extra.
- CLEAR lv_extra.
- WHILE lv_index_start_tmp LE lv_index_end.
- CLEAR: lv_int1,
- lv_int2.
- READTABLE lt_int1 INTO lv_int1 INDEX lv_index_start_tmp.
- READTABLE lt_int2 INTO lv_int2 INDEX lv_index_end_tmp.
- lv_int_tmp = ( lv_int1 * lv_int2 ) MOD gc_unit_size.
- lv_extra = lv_extra + ( lv_int1 * lv_int2 ) DIV gc_unit_size.
- lv_int = lv_int + lv_int_tmp.
- lv_extra = lv_extra + lv_int DIV gc_unit_size.
- lv_int = lv_int MOD gc_unit_size.
- lv_index_start_tmp = lv_index_start_tmp + 1.
- lv_index_end_tmp = lv_index_end_tmp - 1.
- ENDWHILE.
- APPEND lv_int TO lt_int.
- IF lv_index_end LT lv_count.
- lv_index_end = lv_index_end + 1.
- ELSEIF lv_index_start LT lv_count.
- lv_index_start = lv_index_start + 1.
- ELSEIF lv_index_start EQ lv_count AND
- lv_index_end EQ lv_count.
- EXIT.
- ENDIF.
- ENDDO.
- IF lv_extra ISNOTINITIAL.
- APPEND lv_extra TO lt_int.
- ENDIF.
- delete_leading_zeros( ).
- int_to_string( ).
- ENDMETHOD. "multiply
- METHODdivide.
- DATA: lt_int1 TYPE int4_table,
- lt_int2 TYPE int4_table,
- lv_int TYPEi,
- lv_int1 TYPEi,
- lv_int2 TYPEi,
- lv_string1 TYPE string,
- lv_string2 TYPE string,
- lv_index TYPEi,
- lv_count TYPEi,
- lv_extra TYPEi,
- lv_sign1 TYPEi,
- lr1 TYPEREFTO lcl_bignum,
- lr2 TYPEREFTO lcl_bignum,
- lr_guess TYPEREFTO lcl_bignum,
- lr_step TYPEREFTO lcl_bignum,
- lv_step_str TYPE string,
- lv_step TYPEi,
- lr_temp TYPEREFTO lcl_bignum.
- CREATE OBJECT lr1.
- CREATE OBJECT lr2.
- lr1->copy_values( i_obj1 ).
- lr2->copy_values( i_obj2 ).
- * quotient is zero when divisor is bigger than dividend
- CHECK lr1->abs_compare( lr2 ) EQ gc_gt.
- IF lr1->get_sign( ) LT0.
- lr1->invert_sign( ).
- ENDIF.
- IF lr2->get_sign( ) LT0.
- lr2->invert_sign( ).
- ENDIF.
- * start with 1, keep multiplying by gc_unit_size
- CREATE OBJECT lr_guess
- EXPORTING
- i_value = '1'.
- CREATE OBJECT lr_temp.
- CREATE OBJECT lr_step.
- lv_step = gc_unit_size.
- DO.
- DO.
- lr_temp->multiply( i_obj1 = lr_guess
- i_obj2 = lr2 ).
- CASE lr_temp->abs_compare( lr1 ).
- WHEN gc_eq.
- copy_values( lr_guess ).
- RETURN.
- WHEN gc_gt.
- EXIT.
- WHEN gc_lt.
- * increment
- * lv_step = gc_unit_size.
- copy_values( lr_guess ).
- lv_step_str = lv_step.
- lr_step->set_data( lv_step_str ).
- lr_guess->multiply( i_obj1 = lr_guess
- i_obj2 = lr_step ).
- * when others.
- ENDCASE.
- ENDDO.
- lr_guess->copy_values( me ).
- lv_step = SQRT( lv_step ).
- IF lv_step EQ1.
- EXIT.
- ENDIF.
- ENDDO.
- * answer is between guess and guess * 2
- * now add steps to move closer to answer
- * lv_step = gc_unit_size.
- lr_step->copy_values( lr_guess ).
- DO.
- DO.
- lr_temp->multiply( i_obj1 = lr_guess
- i_obj2 = lr2 ).
- CASE lr_temp->abs_compare( lr1 ).
- WHEN gc_eq.
- copy_values( lr_guess ).
- RETURN.
- WHEN gc_gt.
- EXIT.
- WHEN gc_lt.
- * increment
- copy_values( lr_guess ).
- lr_guess->add( i_obj1 = lr_guess
- i_obj2 = lr_step ).
- * when others.
- ENDCASE.
- ENDDO.
- lr_guess->copy_values( me ).
- * quick way to divide by 2
- lr_step->divide_by_2( ).
- IF lr_step->get_int( ) ISINITIAL.
- EXIT.
- ENDIF.
- ENDDO.
- * int_to_string( ).
- ENDMETHOD. "divide
- METHODmod.
- DATA lr_div TYPEREFTO lcl_bignum.
- CREATE OBJECT lr_div.
- lr_div->divide( i_obj1 = i_obj1
- i_obj2 = i_obj2 ).
- lr_div->multiply( i_obj1 = lr_div
- i_obj2 = i_obj2 ).
- subtract( i_obj1 = i_obj1
- i_obj2 = lr_div ).
- ENDMETHOD. "mod
- METHOD power.
- DATA: lr_power TYPEREFTO lcl_bignum,
- lr_one TYPEREFTO lcl_bignum.
- CREATE OBJECT lr_one
- EXPORTING
- i_value = '1'.
- CREATE OBJECT lr_power.
- lr_power->copy_values( i_obj2 ).
- lr_power->delete_leading_zeros( ).
- * only positive power
- IF get_sign( ) NE lr_power->get_sign( ).
- lr_power->invert_sign( ).
- ENDIF.
- * result 1 when power is 0
- set_data( '1' ).
- DO.
- IF lr_power->get_int( ) ISINITIAL.
- EXIT.
- ELSE.
- lr_power->subtract( i_obj1 = lr_power
- i_obj2 = lr_one ).
- multiply( i_obj1 = me
- i_obj2 = i_obj1 ).
- ENDIF.
- ENDDO.
- ENDMETHOD. "power
- ENDCLASS. "lcl_bignum IMPLEMENTATION
- START-OF-SELECTION.
- DATA lr1 TYPEREFTO lcl_bignum.
- DATA lr2 TYPEREFTO lcl_bignum.
- DATA lr3 TYPEREFTO lcl_bignum.
- TRY .
- CREATE OBJECT lr1
- EXPORTING
- i_value = '6584389034291301828999'.
- * i_negative = 'X'.
- CREATE OBJECT lr2
- EXPORTING
- i_value = '533335555511111'.
- CREATE OBJECT lr3.
- lr3->add( i_obj1 = lr1
- i_obj2 = lr2 ).
- * lr3->subtract( i_obj1 = lr1
- * i_obj2 = lr2 ).
- * lr3->multiply( i_obj1 = lr1
- * i_obj2 = lr2 ).
- * lr3->divide( i_obj1 = lr1
- * i_obj2 = lr2 ).
- * lr3->mod( i_obj1 = lr1
- * i_obj2 = lr2 ).
- lr1->set_data( '512' ).
- lr2->set_data( '17' ).
- lr3->power( i_obj1 = lr1
- i_obj2 = lr2 ).
- lr2->set_data( '2773' ).
- lr3->mod( i_obj1 = lr3
- i_obj2 = lr2 ).
- CATCH cx_root.
- ENDTRY.
/.