Integer Arithmetic Operations on Large Numbers
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.
- PUBLIC SECTION.
- METHODS constructor IMPORTING i_value TYPE string OPTIONAL
- i_negative TYPE c OPTIONAL.
- METHODS get_sign RETURNING value(r_sign) TYPE i.
- 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 TYPE c OPTIONAL.
- METHODS add IMPORTING i_obj1 TYPE REF TO lcl_bignum
- i_obj2 TYPE REF TO lcl_bignum.
- METHODS subtract IMPORTING i_obj1 TYPE REF TO lcl_bignum
- i_obj2 TYPE REF TO lcl_bignum.
- METHODS multiply IMPORTING i_obj1 TYPE REF TO lcl_bignum
- i_obj2 TYPE REF TO lcl_bignum.
- METHODS divide IMPORTING i_obj1 TYPE REF TO lcl_bignum
- i_obj2 TYPE REF TO lcl_bignum.
- METHODS mod IMPORTING i_obj1 TYPE REF TO lcl_bignum
- i_obj2 TYPE REF TO lcl_bignum.
- METHODS power IMPORTING i_obj1 TYPE REF TO lcl_bignum
- i_obj2 TYPE REF TO lcl_bignum.
- METHODS invert_sign.
- METHODS copy_values IMPORTING i_obj TYPE REF TO lcl_bignum.
- METHODS abs_compare IMPORTING i_obj TYPE REF TO lcl_bignum
- RETURNING value(r_result) TYPE i.
- PROTECTED SECTION.
- METHODS int_to_string.
- METHODS split_into_int.
- METHODS delete_leading_zeros.
- METHODS divide_by_2.
- PRIVATE SECTION.
- DATA lv_string TYPE string.
- DATA lv_sign TYPE i VALUE 1.
- DATA lt_int TYPE int4_table.
- “sqrt of max int4 is 46340
- CONSTANTS: gc_unit_size TYPE i VALUE 10000,
- gc_unit_digits TYPE i VALUE 4,
- gc_gt TYPE i VALUE 1, “greater than
- gc_lt TYPE i VALUE 2, “less than
- gc_eq TYPE i VALUE 0. “equals
- ENDCLASS. “lcl_bignum DEFINITION
- *———————————————————————-*
- * CLASS lcl_bignum IMPLEMENTATION
- *———————————————————————-*
- *
- *———————————————————————-*
- CLASS lcl_bignum IMPLEMENTATION.
- METHOD constructor.
- IF i_value IS SUPPLIED.
- IF i_value CO ‘ 1234567890’.
- lv_string = i_value.
- CONDENSE lv_string.
- ENDIF.
- split_into_int( ).
- ENDIF.
- IF i_negative IS SUPPLIED.
- 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 IS SUPPLIED.
- 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 TYPE i.
- lv_string = me->lv_string.
- WHILE lv_string IS NOT INITIAL.
- IF STRLEN( 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 LEFT BY 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 TYPE i,
- lv_count TYPE i.
- CHECK lt_int IS NOT INITIAL.
- lt_temp = lt_int.
- CLEAR lt_int.
- lv_count = LINES( lt_temp ).
- DO lv_count TIMES.
- READ TABLE lt_temp INTO lv_temp INDEX lv_count.
- IF lv_temp IS NOT INITIAL.
- EXIT.
- ENDIF.
- lv_count = lv_count – 1.
- ENDDO.
- IF lv_count IS NOT INITIAL AND
- lv_count LE LINES( lt_temp ).
- APPEND LINES OF lt_temp FROM 1 TO lv_count TO lt_int.
- ENDIF.
- ENDMETHOD. “delete_leading_zeros
- METHOD int_to_string.
- DATA lv_int TYPE i.
- DATA lv_char TYPE n LENGTH gc_unit_digits.
- LOOP AT 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 TYPE i,
- lv_int2 TYPE i,
- lv_max TYPE i.
- lt_int2 = i_obj->get_int( ).
- lv_max = LINES( lt_int ).
- IF LINES( lt_int2 ) GT lv_max.
- lv_max = LINES( lt_int2 ).
- ENDIF.
- WHILE lv_max GT 0.
- CLEAR: lv_int,
- lv_int2.
- READ TABLE lt_int INTO lv_int INDEX lv_max.
- READ TABLE 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 TYPE i,
- lt_temp TYPE int4_table,
- lv_mod TYPE i,
- lv_count TYPE i.
- lv_count = LINES( lt_int ).
- DO lv_count TIMES.
- READ TABLE lt_int INTO lv_int INDEX lv_count.
- IF sy-subrc EQ 0.
- lv_int = lv_int + lv_mod * gc_unit_size.
- lv_mod = lv_int MOD 2.
- lv_int = lv_int DIV 2.
- INSERT lv_int INTO lt_temp INDEX 1.
- 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
- METHOD add.
- DATA: lt_int1 TYPE int4_table,
- lt_int2 TYPE int4_table,
- lv_int TYPE i,
- lv_int1 TYPE i,
- lv_int2 TYPE i,
- lv_index TYPE i,
- lv_count TYPE i,
- lv_extra TYPE i.
- 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 LT LINES( 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.
- READ TABLE lt_int1 INTO lv_int1 INDEX lv_index.
- READ TABLE 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
- METHOD subtract.
- DATA: lt_int1 TYPE int4_table,
- lt_int2 TYPE int4_table,
- lv_int TYPE i,
- lv_int1 TYPE i,
- lv_int2 TYPE i,
- lv_index TYPE i,
- lv_count TYPE
i, - lv_extra TYPE i.
- FIELD-SYMBOLS: <fs_int> TYPE i.
- 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 LT LINES( 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.
- READ TABLE lt_int1 INTO lv_int1 INDEX lv_index.
- READ TABLE lt_int2 INTO lv_int2 INDEX lv_index.
- lv_int = lv_extra + lv_int1 – lv_int2.
- CLEAR lv_extra.
- IF lv_int LT 0.
- 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 IS NOT INITIAL.
- lv_sign = lv_sign * lv_extra.
- LOOP AT lt_int ASSIGNING <fs_int>.
- IF sy-tabix EQ 1.
- <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
- METHOD multiply.
- DATA: lt_int1 TYPE int4_table,
- lt_int2 TYPE int4_table,
- lv_int TYPE i,
- lv_int1 TYPE i,
- lv_int2 TYPE i,
- lv_int_tmp TYPE i,
- lv_index TYPE i,
- lv_index_start TYPE i,
- lv_index_end TYPE i,
- lv_index_start_tmp TYPE i,
- lv_index_end_tmp TYPE i,
- lv_count TYPE i,
- lv_carry TYPE i,
- lv_extra TYPE i.
- lt_int1 = i_obj1->get_int( ).
- lt_int2 = i_obj2->get_int( ).
- lv_count = LINES( lt_int1 ).
- IF lv_count LT LINES( 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 IS NOT INITIAL.
- 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.
- READ TABLE lt_int1 INTO lv_int1 INDEX lv_index_start_tmp.
- READ TABLE 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 IS NOT INITIAL.
- APPEND lv_extra TO lt_int.
- ENDIF.
- delete_leading_zeros( ).
- int_to_string( ).
- ENDMETHOD. “multiply
- METHOD divide.
- DATA: lt_int1 TYPE int4_table,
- lt_int2 TYPE int4_table,
- lv_int TYPE i,
- lv_int1 TYPE i,
- lv_int2 TYPE i,
- lv_string1 TYPE string,
- lv_string2 TYPE string,
- lv_index TYPE i,
- lv_count TYPE i,
- lv_extra TYPE i,
- lv_sign1 TYPE i,
- lr1 TYPE REF TO lcl_bignum,
- lr2 TYPE REF TO lcl_bignum,
- lr_guess TYPE REF TO lcl_bignum,
- lr_step TYPE REF TO lcl_bignum,
- lv_step_str TYPE string,
- lv_step TYPE i,
- lr_temp TYPE REF TO 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( ) LT 0.
- lr1->invert_sign( ).
- ENDIF.
- IF lr2->get_sign( ) LT 0.
- 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 EQ 1.
- 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( ) IS INITIAL.
- EXIT.
- ENDIF.
- ENDDO.
- * int_to_string( ).
- ENDMETHOD. “divide
- METHOD mod.
- DATA lr_div TYPE REF TO 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 TYPE REF TO lcl_bignum,
- lr_one TYPE REF TO 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( ) IS INITIAL.
- 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 TYPE REF TO lcl_bignum.
- DATA lr2 TYPE REF TO lcl_bignum.
- DATA lr3 TYPE REF TO 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.
/.
Hello Manish,
I am not sure on which version of SAP you are on; but if you are on releases > 702 you can use the decimal floating types(DECFLOAT) to get faster & more accurate results (ref - http://help.sap.com/abapdocu_740/en/abennews-71-decfloat.htm).
I would rather use the proper type instead of using some workaround which is not full/fool proof. The choice on which numeric type to use can be made by keeping these points in mind - http://help.sap.com/abapdocu_740/en/abenselect_numeric_type_guidl.htm.
I have used the type DECFLOAT34 to illustrate the calculations (using the numbers you have used in your example) -
Of course there is a limit to the accuracy using this data type as well, i would not argue that. But IMO, SAP is a business software & i'll excuse it if it does not perform calculations with scientific accuracy 😛
BR,
Suhas
Hi Suhas
Thanks for the illustration. I am also on 702 release.
My motive was to calculate the exact answer for ( 512 ^ 17 ) mod 2773.
Decfloat34 gave 0 answer, and that is why program was written, purely as an academic exercise.
Hi Manish,
Q) Could you please tell me why this was used in the constructor "split_into_int( )".
Googling for this I have received only replies pertaining to C#.
Q) What is this kind of function called ? And are there others like this.
Thanks.
Hi Starlet
Split_into_int( ) is a method of same local class lcl_bignum. Code is enclosed between Method Split_into_int and Endmethod statements.
Since we are passing continuous string like '1234567890' during Create Object, that string is broken down into internal table of integers using method split_into_int( ).
After method call, table of integers would have 3 integers, 12(last entry), 3456, 7890 (first entry).
It is a protected instance method of local class. Definition is kept in protected section because method is called only by other methods of same class, and not from outside.
Sorry Manish,
My bad. I did not see the code thoroughly 😥 . Thought it was some ABAP standard functionality or something 🙁 . Silly me
Thanks Manish.
While going through Project Euler, i found this class being very helpful at first. Later on, it showed a bunch of errors and limitations. Thus i rewrote it for elemination of errors, better performance, better usability and extended functionality.
class documentation:
CL Z04_BIGINT
____________________________________________________
Kurztext
Big Integer Arithmetik auf Float-Arrays
Funktionalität
Stellt BigInteger-Arithmetik auf Basis von internem floating point array bereit.
Kann bis zu Speicherlimit/Typlimit Zahlenlänge verarbeiten (Begrenzung nur durch die Maximalgröße einer internen Tabelle für Floats bzw. den Integer-Wertebereich für die Indizierung von Ziffern).
Philosophie der Schnittstellen-Organisation ist, dass - außer in "get..." und "cmp..."-Funktionen - immer das aktuelle Objekt manipuliert wird, gegebenenfalls unter Hinzuziehung eines weiteren Objekts. Das heißt, dass der erste Operand aller Operationen immer das aktuelle Objekt ist. Das bedeutet weiterhin, dass alle Operationen mit zwei Operanden (add, mul, ...) - der erste davon immer das aktuelle Objekt - inkrementell auf das aktuelle Objekt wirken.
Zusammen mit dem Umstand, dass alle Operationen, die das aktuelle Objekt verändern, eine Referenz auf das aktuelle Objekt liefern, bedeutet dies, dass Operationen nahtlos verkettet werden können:
a->seti( 1 )->add( b )->mul( c ).
Eine Ausnahme davon stellt "divx()" dar: Dieses kann den Rest als return-Argument zurückliefern, manipuliert aber auch inkrementell das aktuelle Objekt. Der Name bekommt deshalb keine Vorsilbe GET, aber nach "->divx()" kann nicht nahtlos weiter verkettet werden. Für letzteres gibt es "div()" und "mod()" getrennt. "div()" und "mod()" benutzen intern "divx()". "divx" puffert statisch den Rest. Dieser kann bei einem anschließenden "mod()" mit denselben Argumenten unmittelbar danach ohne Neuberechnung abgerufen werden.
Weiterhin stellen die Vergleichsfunktionen ("cmp()" und "abscmp()") eine Ausnahme von dieser Regel dar. Die Vorsilbe "get" würde bei denen einfach albern wirken. Der Vergleich liefert "-1", wenn das aktuelle Objekt kleiner ist als das Argument "x", "+1" wenn größer und "0" wenn gleich.
Es gibt eine weitere Klasse "z04_bigintx" (mit Suffix "x"), die mit DecFloat34 arbeitet ud Zahlen mit mehr als 100 Ziffern deutlich schneller verarbeiten kann. Die hier vorliegende Klasse "z04_bigint" ist also eher für "kleine große" Zahlen geeignet, während "z04_bigintx" für "echt große große" Zahlen sinnvoll ist.
Beispiel
data: a type ref to BigInt,
b type ref to BigInt,
s type string.
create object a, b.
a->setstr( '+0123e200' ).
s = b->setstr( '-0123e200' )->add( a )->getstr( ).
write: / s. " liefert: 0
Hinweise
Setter / Getter:
Es gibt eine ganze Palette von Funktionen zum Einstellen und Abfragen eines Wertes - aus Performance-Gründen: Ein Teil dieser Funktionen wird intern für die Implementierung eingesetzt. Sie unterscheiden sich von anderen Funktionen durch einen Präfix "set" bzw. "get". Sie unterscheiden sich untereinander durch ein Suffix, welches den Typ des Arguments kennzeichnet (weil von SAP bisher kein Überladen für nutzerdefinierte Funktionen bereitgestellt wird). Die schnellsten davon sind die für Integers (mit "i" als Suffix) bzw. Floats (mit "f" als Suffix). Für alle nicht explizit unterstützten Zahlenformate ist eine gemeinsame Schnittstelle über String ("setstr()" / "getstr()") vorgesehen.
"setstr()" versteht Vorzeichen ("+" und "-") und Zehnerpotenzen in der üblichen Notationsform ('+123e100') und toleriert führende Nullen sowie Leerzeichen um das ganze drumrum (' -00001234e567 ' ).
Alle anderen Setter (mit Zahlen-Argument) können optional einen extra Integer als Zehner-Exponent aufnehmen.
"setf()" ist nicht wirklich für die Übergabe sehr großer Zahlen vorgesehen, denn solche float-Werte haben zu Ganzahlen gewandelt eh nur einen Bruchteil ihrer Stellen (die höchsten 15) sinnvoll gesetzt. Der Rest ist Schrott oder Null. Mit "setf()" soll lediglich eine vereinfachte Übernahme von floats bis maximal 10^15 ermöglicht werden, wenn diese ursprünglich als float vorliegen. Dies wird unter anderem intern für das Einstellen von Schätzwerten beim Dividieren genutz. Falls also viele hängende Nullen übergeben werden sollen, ist dazu das optionale Argument "exp" zu nutzen!
Analoges gilt für "setdf()".
Die Funktionen "getf()" und "getdf()" liefern 8-byte-floats bzw. 16-byte-decfloats als Mantisse und einen extra Integer als Zehner-Exponent zurück. Letzteres ist notwendig, weil die Exponentenbereiche gewöhnlicher floats nur für winzige Bigints ausreichen (lediglich für ein paar hundert bis ein paar tausend Stellen). Die Mantissen stellen einen Näherungswert mit mindestens 15 bzw. 29 Dezimalstellen dar.
Alle Getter liefern den abgefragten Wert zurück und unterbrechen damit die Verkettbarkeit.
Alle Setter liefern eine Referenz auf das aktuelle Objekt zurück und können somit bevorzugt zum Start von Verkettungssequenzen genutzt werden.
=============================
Ausnahmen von der syntaktischen Systematik:
Die Syntax-Ausnahme beim "divx()" ist gewollt. Es soll dort verwendet werden, wo beim Dividieren der Rest sofort gegriffen werden soll. Ist dagegen nahtloses Verketten gewünscht, kann "div()" zusammen mit einem späteren "mod()" genutzt werden, ohne dass für letzteres die Berechnung wiederholt wird. Der Rest wird intern statisch zwischengespeichert. Wenn allerdings zwischen einem "div()" und dem dazugehörenden "mod()" eine andere Operation "div()" oder "divx()" (mit anderen Operanden) aufgerufen wurde, wird beim Aufruf von "mod()" die Division erneut ausgeführt. Wenn der Programmierer hier Blödsinn baut, hat er vom internen Zwischenspeichern nichts. Die Ergebnisse sind dennoch immer korrekt.
Es könnte zusätzlich auch Funktionen mit zwei Operanden geben (Präfix "e"), die jeweils alle ihre Operanden als Argumente übernehmen. Das Beispiel aus dem einleitenden Abschnitt mit dieser Schnittstellenvariante sähe so aus:
a->emul( x = a->eadd( x = a->seti( 1 ) y = b ) y = c ).
-> Die Syntax wäre auf jeden Fall aufwendiger und bei Verkettungen schlechter lesbar. Momentan sehe ich keinen Anlass, dies zu implementieren.
=============================
Warum benutzt Du nicht einfach den Typ "packed" mit 16 Bytes? Oder "DecFloat34"?
Weil deren Wertebereich nur bis 31 bzw. 34 Dezimalstellen reicht. Schon für mathematische Aufgaben auf Anfänger-Niveau (vor Beginn eines Mathematik-Studiums, nur bis Klasse 12/13) sind größere Wertebereiche zwingend notwendig. Schon Mathematiker bei den alten Arabern und im europäischen Mittelalter haben mit größeren Zahlen experimentiert (die sie damals von Hand berechneten - das waren echte Enthusiasten!) . Wer sich für solche Experimente nicht begeistern möchte, möge die Finger davon lassen. Wer in die Fußstapfen der Entwickler von moderner Verschlüsselung treten möchte oder sich sonstwie für alle möglichen mathematischen Fragestellungen interessiert, muss sich zwingend auch auf solche akademischen Zahlenexperimente mit großen Zahlen einlassen. 4k-RSA etwa braucht für normale Funktion Zahlen mit etwa 1200 Dezimalstellen (in üblichen "richtigen" Computern werden solche 4-K-RSA-Faktoren als 500 Byte-Werte gespeichert, aber SAP stellt Bytes nur über Umwege bereit, die sich nur marginal für direkte Verarbeitung eignen und auch dies nur in sehr kleinen Blöcken (4 Byte)). Mit DecFloat34 könnte man etwa gerade so die Berechnung von veralteten 64-bit-Hash-Verfahren nachstellen (bzw. entwickeln). Natürlich wird man echte Krypto-Bibliotheken niemals in ABAP schreiben - aus besten auf der Hand liegenden Gründen. Aber ABAP - wenn man es nun schon mal verpflichtend als Sprache im beruflichen Alltag verwenden muss - lässt sich nichtsdestotrotz für Experimente - zum Lernen und Forschen - perfekt einsetzen. Wenn man es ein wenig aufbohrt.
=============================
Performance:
ABAP verbrät pro elementarem Statement der Sprache größenordnungsmäig 100 bis 200 CPU-Takte (analog etwa PHP). Das entspricht auf derzeitigen CPUs einigen zig Nanosekunden pro simpelster Anweisung. Da jede BigInt-Funktion - selbst im einfachsten Fall etwa der Addition - aus mindestens um 10 elementaren Anweisungen einschließlich Argumenttests und Sonderfallbehandlung besteht, ist keine BigInt-Funktion unter ein paar hundert Nanosekunden zu bekommen. Dazu kommen Operationswiederholungen proportional der Menge der chunks in den Zahlen.
Der Aufwand für Add/Cmp/Set/Get wächst linear mit der Zahlengröße.
Der Aufwand für Mul wächst quadratisch.
Der Aufwand für Pow wächst quadratisch zur Basis / linear zum Exponenten.
Der Aufwand für Div/Mod/PowMod wächst kubisch.
Weiterführende Informationen
Anwendungsbereiche: akademische Mathematik, wissenschaftliche Berechnungen, mathematische Experimente, Erlernen der Sprache ABAP, "Euler Project" ab Aufgabe 13:
https://projecteuler.net/archives
=============================
Anregungen zur Bibliothek: Manish Kumar "Integer Arithmetic Operations on Large Numbers"
https://blogs.sap.com/2013/09/09/integer-arithmetic-operations-on-large-numbers/
Dieser gegenüber fehlerbereinigte und durch Nutzung von floats statt integers stark beschleunigte Operationen (Addition/Multiplikation: 3-fach, Division: 1000-fach, Potenz: zig bis zigmilliardenfach - je nach Zahlengröße).
Hi,
can you share the class code that handle the very very large numbers Z04_bigintx
Disclaimer: Unfortunately, i'm currently not using any SAP system, so this post is from an archive, with no maintenance since two years. I had successfully used it up to euler project / problem 67. No guarantee to be error free beyond that application.