Oracle database experiences

Oracle database blog

Working with large integers using PL/SQL – Part V – Factorial

Factorial

Once we have defined “multiply_li” we can compute the factorial of a large integer whose int_part0 is greater or equal than 0. The formula is:

  • n! = 1 * 2 * … * (n-1) * n,
  • By convention 0! = 1.

The member procedure “factorial_li” is shown as follow:

...
  MEMBER PROCEDURE factorial_li
   IS 
      v_large_integer1 large_integer;
      v_large_integer2 large_integer;
      v_index          NUMBER;
   BEGIN
      IF SELF.int_part1 > 0 OR SELF.int_part2 > 0 THEN
         RAISE_APPLICATION_ERROR(-20004,'Factorial_li:Large integer too big!');
      ELSIF SELF.int_part0 < 0 THEN
         RAISE_APPLICATION_ERROR(-20005,'Factorial_li:Factorial should be >= 0!');
      END IF;

      v_large_integer2 := large_integer(1,0,0); 
      FOR v_index IN 1..SELF.int_part0
      LOOP 
         v_large_integer1 := large_integer(v_index,0,0); 
         v_large_integer2.multiply_li(v_large_integer1);
      END LOOP;
      SELF := v_large_integer2;
   END factorial_li;
...

Modified specifications

You will find below the modified specifications:

CREATE OR REPLACE TYPE large_integer AS OBJECT (
   int_part0 NUMBER(37),
   int_part1 NUMBER(37),
   int_part2 NUMBER(37),
   --
   CONSTRUCTOR FUNCTION large_integer
   RETURN SELF AS RESULT,
   --
   CONSTRUCTOR FUNCTION large_integer(int_part0 number,int_part1 number,int_part2 number)
   RETURN SELF AS RESULT,
   --
   CONSTRUCTOR FUNCTION large_integer(p_large_integer VARCHAR2)
   RETURN SELF AS RESULT,
   --
   MEMBER PROCEDURE display,
   --
   MEMBER PROCEDURE add_li(p_large_integer large_integer), 
   --
   MEMBER PROCEDURE multiply_digit(p_digit NUMBER),
   --
   MEMBER FUNCTION to_varchar(p_mode CHAR DEFAULT 'N')
   RETURN VARCHAR2,
   --
   MEMBER FUNCTION length_li 
   RETURN NUMBER,
   --
   MEMBER PROCEDURE multiply_10power(p_power NATURALN),
   --
   MEMBER PROCEDURE multiply_li(p_large_integer large_integer),
   --
   MEMBER PROCEDURE factorial_li 
) INSTANTIABLE NOT FINAL;

Modified body

You will find below the modified body:

CREATE OR REPLACE TYPE BODY large_integer
IS
   CONSTRUCTOR FUNCTION large_integer 
   RETURN SELF AS RESULT 
   IS
   BEGIN
      int_part0:=0;
      int_part1:=0;
      int_part2:=0;
      RETURN;
   END large_integer;
   --
   CONSTRUCTOR FUNCTION large_integer(int_part0 number,int_part1 number,int_part2 number) 
   RETURN SELF AS RESULT 
   IS
   BEGIN
      SELF.int_part0:=int_part0;
      SELF.int_part1:=int_part1;
      SELF.int_part2:=int_part2; 
      RETURN;
   END large_integer;
   --
   CONSTRUCTOR FUNCTION large_integer(p_large_integer VARCHAR2)
   RETURN SELF AS RESULT
   IS
      v_li_length     INTEGER;
      v_large_integer VARCHAR2(200);
   BEGIN
      IF LENGTH(p_large_integer) > 111 THEN
         RAISE_APPLICATION_ERROR(-20002,'Large integer too big!');
      ELSE

         v_large_integer := LPAD(p_large_integer,111,'0');
         v_li_length := LENGTH(v_large_integer);

         SELF.int_part0 := TO_NUMBER(SUBSTR(v_large_integer,v_li_length-36,37));
         SELF.int_part1 := TO_NUMBER(SUBSTR(v_large_integer,v_li_length-73,37));
         SELF.int_part2 := TO_NUMBER(SUBSTR(v_large_integer,v_li_length-110,37));

      END IF;
      
      RETURN;
   END large_integer;
   --
   MEMBER PROCEDURE display 
   IS
   BEGIN
      DBMS_OUTPUT.PUT_LINE('#' 
                        || SELF.to_varchar);
   END display;
   --
   MEMBER PROCEDURE add_li(p_large_integer large_integer) 
   IS
      v_carry    NUMBER    :=0;
      v_sub_add  NUMBER(38);
   BEGIN
      v_sub_add := SELF.int_part0 + p_large_integer.int_part0; 

      v_carry := TO_NUMBER(SUBSTR(LPAD(TO_CHAR(v_sub_add),38,'0'),1,1));

      SELF.int_part0 := TO_NUMBER(SUBSTR(LPAD(TO_CHAR(v_sub_add),38,'0'),2));

      v_sub_add := SELF.int_part1 + p_large_integer.int_part1 + v_carry;

      v_carry := TO_NUMBER(SUBSTR(LPAD(TO_CHAR(v_sub_add),38,'0'),1,1));

      SELF.int_part1 := TO_NUMBER(SUBSTR(LPAD(TO_CHAR(v_sub_add),38,'0'),2));

      v_sub_add := SELF.int_part2 + p_large_integer.int_part2 + v_carry;

      v_carry := TO_NUMBER(SUBSTR(LPAD(TO_CHAR(v_sub_add),38,'0'),1,1)); 

      SELF.int_part2 := TO_NUMBER(SUBSTR(LPAD(TO_CHAR(v_sub_add),38,'0'),2));

      IF v_carry > 0 THEN 
         RAISE_APPLICATION_ERROR(-20000,'Add_li:Large integer too big!'); 
      END IF;
     
   END add_li;
   --
   MEMBER PROCEDURE multiply_digit(p_digit NUMBER)
   IS
      v_large_integer large_integer;
      v_index         PLS_INTEGER;
   BEGIN
      IF p_digit NOT IN (0,1,2,3,4,5,6,7,8,9) 
      THEN
         RAISE_APPLICATION_ERROR(-20001,'Multiply_digit:p_digit should be between 0 and 9!');
      ELSIF p_digit = 0 
      THEN
         int_part0:=0;
         int_part1:=0;
         int_part2:=0;
      ELSE
         v_large_integer := SELF;
         FOR v_index IN 1..(p_digit-1)
         LOOP
            SELF.add_li(v_large_integer);  
         END LOOP;
      END IF;
   END multiply_digit; 
   -- 
   MEMBER FUNCTION to_varchar(p_mode CHAR DEFAULT 'N')
   RETURN VARCHAR2
   IS
      v_large_integer_char VARCHAR2(200);
   BEGIN
      
      v_large_integer_char := LPAD(TO_CHAR(SELF.int_part2),37,'0') 
                           || LPAD(TO_CHAR(SELF.int_part1),37,'0') 
                           || LPAD(TO_CHAR(SELF.int_part0),37,'0');
      IF p_mode = 'T' THEN
         v_large_integer_char := NVL(LTRIM(v_large_integer_char,'0'),'0'); 
      END IF;

      RETURN v_large_integer_char;
   END to_varchar;
   --
   MEMBER FUNCTION length_li 
   RETURN NUMBER
   IS
      v_length NUMBER;
   BEGIN
      v_length := LENGTH(SELF.to_varchar('T'));
      RETURN v_length;
   END length_li;
   -- 
   MEMBER PROCEDURE multiply_10power(p_power NATURALN)
   IS
     v_large_integer_char VARCHAR2(1000);
     v_length             NUMBER;
   BEGIN
      v_large_integer_char := SELF.to_varchar
                           || LPAD('0',p_power,'0');
  
      v_length := LENGTH(v_large_integer_char);

      IF LENGTH(LTRIM(v_large_integer_char,'0')) > 111 THEN 
         RAISE_APPLICATION_ERROR(-20003,'Multiply_10power:Large integer too big!'); 
      END IF;

      SELF.int_part0 := TO_NUMBER(SUBSTR(v_large_integer_char,v_length-36,37));
      SELF.int_part1 := TO_NUMBER(SUBSTR(v_large_integer_char,v_length-73,37));
      SELF.int_part2 := TO_NUMBER(SUBSTR(v_large_integer_char,v_length-110,37));
   END multiply_10power; 
   --
   MEMBER PROCEDURE multiply_li(p_large_integer large_integer)
   IS
      v_large_integer_char VARCHAR2(1000);
      v_large_integer1     large_integer;
      v_large_integer2     large_integer := large_integer;
      v_length             INTEGER;
      v_index              PLS_INTEGER;
   BEGIN
      v_large_integer_char := p_large_integer.to_varchar('T');
      v_length := LENGTH(v_large_integer_char);
 
      FOR v_index IN 1..v_length
      LOOP 
         v_large_integer1 := SELF;
         v_large_integer1.multiply_digit(TO_NUMBER(SUBSTR(v_large_integer_char,v_length-v_index+1,1)));
         v_large_integer1.multiply_10power(v_index-1);
         v_large_integer2.add_li(v_large_integer1);
      END LOOP;
      SELF := v_large_integer2;
   END multiply_li;
   --
   MEMBER PROCEDURE factorial_li
   IS 
      v_large_integer1 large_integer;
      v_large_integer2 large_integer;
      v_index          NUMBER;
   BEGIN
      IF SELF.int_part1 > 0 OR SELF.int_part2 > 0 THEN
         RAISE_APPLICATION_ERROR(-20004,'Factorial_li:Large integer too big!');
      ELSIF SELF.int_part0 < 0 THEN
         RAISE_APPLICATION_ERROR(-20005,'Factorial_li:Factorial should be >= 0!');
      END IF;

      v_large_integer2 := large_integer(1,0,0); 
      FOR v_index IN 1..SELF.int_part0
      LOOP 
         v_large_integer1 := large_integer(v_index,0,0); 
         v_large_integer2.multiply_li(v_large_integer1);
      END LOOP;
      SELF := v_large_integer2;
   END factorial_li;
END;

Examples:

MATH@pdb1> DECLARE
  2     v_li1 large_integer;
  3     v_li2 large_integer;
  4  BEGIN
  5     DBMS_OUTPUT.PUT_LINE('-- Example9: Factorial_li');
  6     v_li1 := large_integer('75');
  7     v_li1.display();
  8     v_li1.factorial_li;
  9     v_li1.display();
 10
 11     DBMS_OUTPUT.PUT_LINE('-- Example10: Factorial_li');
 12     v_li1 := large_integer('4');
 13     v_li1.display();
 14     v_li1.factorial_li;
 15     v_li1.display();
 16
 17  END;
 18  /
-- Example9: Factorial_li
#000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000075
#024809140811395398091946477116594033660926243886570122837795894512655842677572867409443815424000000000000000000
-- Example10: Factorial_li
#000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000004
#000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000024

PL/SQL procedure successfully completed.

Remark:

  • Even is we have limited factorial_li to int_part0 only, 9999999999999999999999999999999999999! is a very very large integer!
Advertisements

Leave a Reply

Fill in your details below or click an icon to log in:

WordPress.com Logo

You are commenting using your WordPress.com account. Log Out / Change )

Twitter picture

You are commenting using your Twitter account. Log Out / Change )

Facebook photo

You are commenting using your Facebook account. Log Out / Change )

Google+ photo

You are commenting using your Google+ account. Log Out / Change )

Connecting to %s

%d bloggers like this: