Lab 5: Arithmetic with large integers

Handed out: Feb 22

Due on: Mar 6


There is a limit on how large an integer the ML programming language can handle.

cherokee[67] sml
Standard ML of New Jersey, Version 110.0.3, January 30, 1998
- val x=123456789;
val x = 123456789 : int
- val x=1234567890;
stdIn:9.6-9.16 Error: int constant too large
We want to augment the ML language with functions Add and Multiply that can handle huge nonnegative integers. Since we cannot use the regular ML integers (as the above example shows), we will represent our integers with strings. An example for the use of Add and Multiply is given as follows:
- Add("123456789123456789","987654321987654321"); 
val it = "1111111111111111110" : string 
- Multiply("123456789123456789","987654321987654321");
val it = "121932631356500531347203169112635269" : string
To make the problem easier, we assume that the huge integers that we need to handle are nonnegative. That is, Add and Multiply both take arguments that are strings of digits. Negative signs are not allowed in the arguments given to Add and Multiply.

We will implement Add and Multiply in a number of steps.


Consider an example of a huge integer "123456789" which we represent as a string. In order that we can process the integer, we need to represent it as a list of digits. The predefined ML function explode will turn a given string into a list of characters. That is,

explode("123456789")
returns the character list
head
  |
 \|/
[#"1", #"2", #"3", #"4", #"5", #"6", #"7", #"8", #"9" ]
with the head of the list being "1".

Correspondingly, the function implode takes a character list and returns the corresponding string by concatenating the characters in the given list. That is, implode is the converse of explode.


1. Write a function to_int_list( cl: char list ) such that given a list of characters of digits, say

head
  |
 \|/
[#"1", #"2", #"3", #"4", #"5", #"6", #"7", #"8", #"9" ]
, the function returns the corresponding list of integers, like
head
  |
 \|/
[ 1, 2, 3, 4, 5, 6, 7, 8, 9 ]
Hint: Given a character #"7", the expression ord(#"7") - ord(#"0") returns the integer value 7. The same is true for other decimal digit characters.

A template of the function is given as follows:

fun to_int_list( cl: char list ) =
    if cl = nil then 
       nil
    else 
       (* to be completed *) :: (* to be completed *) ;

We also need a function that does the converse of to_int_list.

2. Write a function to_char_list( il: int list ) such that given a list of digits, say

head
  |
 \|/
[ 1, 2, 3, 4, 5, 6, 7, 8, 9 ]
, the function returns the corresponding list of chars, like
head
  |
 \|/
[#"1", #"2", #"3", #"4", #"5", #"6", #"7", #"8", #"9" ]
Hint: Given an integer 7, the expression chr(7 + ord(#"0")) returns the char value 7. The same is true for other digits.

A template of the function is given as follows:

fun to_char_list( il: int list ) =
    if il = nil then 
       nil
    else 
       (* to be completed *) :: (* to be completed *) ;

Suppose we have two integers "123456789" and "234567890" that we want to add together. By applying to_int_list to the two integers, we have two lists of integers

head
  |
 \|/
[ 1, 2, 3, 4, 5, 6, 7, 8, 9 ]
and
head
  |
 \|/
[ 2, 3, 4, 5, 6, 7, 8, 9, 0 ]
with the heads of the two lists being 1 and 2 respectively.

However, when we do math, we do not perform addition starting from the higher-ordered digits. We start adding from the lower-ordered digits, and move the addition process towards the higher-ordered digits. That is, we add 9 and 0 to give 9, next add 8 and 9 to give 7 with the carry 1, next add 7 and 8 with carry 1 to give 6 with the carry 1, and so on.

Thus, it is more appropriate if we can reverse the list so that the head of the list is at the lowest-ordered digit:

                         head
                          |
                         \|/
[ 1, 2, 3, 4, 5, 6, 7, 8, 9 ]
and
                         head
                          |
                         \|/
[ 2, 3, 4, 5, 6, 7, 8, 9, 0 ]
Note that in the normal way we write lists, these would be [9,8,7,6,5,4,3,2,1] and [0,9,8,7,6,5,4,3,2].
So, we need a function that can reverse a list.


3. Write a function that returns the reverse of a given list. Such a function is given in class and in the textbook. A template for the function is given below:

fun reverse(L) =
    if L = nil then 
       nil
    else 
       (* to be completed *) ;
HINT: look at the "@" operator on page 38 in your ML book.

4. Write a function string_to_list( numstr: string ) that takes a string of digits and returns the reverse of the list of digits. For example, string_to_list( "923074" ) gives

               head
                 |
                \|/
[ 9, 2, 3, 0, 7, 4 ]
which is equivalent to [ 4, 7, 0, 3, 2, 9 ] in the usual list notation. Hint: your function needs to make use of reverse, to_int_list, and  explode.


5. Write a function list_to_string( il: int list ) which is the converse of string_to_list( a: string ). For example, list_to_string( [4, 7, 0, 3, 2, 9 ] ) gives the answer "923074". That is, list_to_string( string_to_list( "923074" ) ) gives "923074". Hint: your function needs to make use of reverse, to_char_list, and implode.


Consider the number "703", the corresponding list representation is as follows:

      head
        |
       \|/
[ 7, 0, 3 ]
Let the list be denoted by L. We can apply functions hd and tl to the list. We have hd(L) = 3 and tl(L) is
      head
        |
       \|/
   [ 7, 0 ]
Let the list tl(L) be denoted by L1. We have hd(L1) = 0 and tl(L1) is
      head
        |
       \|/
      [ 7 ]
Let the list tl(L1) be denoted by L2. We have hd(L2) = 7 and tl(L2) is nil which we denote by L3.

If we attempt to compute hd(L3) and tl(L3), ML responds as follows:

- hd(L3);
uncaught exception Empty
  raised at: boot/list.sml:36.38-36.43
- tl(L3);
uncaught exception Empty
  raised at: boot/list.sml:37.38-37.43
But this may not be desirable. Since we are dealing with a number 703, conceptually we could think of the number as being padded with an infinite number of 0's to its left. That is, 703 can be considered as
.....0000000703
In a sense, the null list nil corresponds to the conceptual infinite list ....00000. That is, for this problem it is more appropriate if hd(nil) returns 0 instead of causing an exception. Similarly, we want tl(nil) to return again nil.


6. Write a function Hd( L: int list ) such that it returns 0 if L is nil, and returns the head element if L is not empty.


7. Write a function Tl( L: int list ) such that it returns nil if L is nil, and returns tl(L) if L is not empty.


We want to write a function add( a: int list, b: int list ) that can add two int lists of digits. We assume that the two lists are ordered in a such a way that the lower-ordered digits are located in the front of the list. That is, the lowest-ordered digit can be accessed by applying the function Hd to the list. Also, the function add returns a list of digits such that the lowest-ordered digit is located at the head of the list.


To implement add( a: int list, b: int list ), we introduce another function add_carry( a: int list, b: int list, carry: int ) where carry is either 0 or 1. The function add_carry adds two lists of decimal digits a and b together with the carry. For example, if a is

         head
           |
          \|/
   [ 4, 3, 7 ]
and b is
         head
           |
          \|/
[ 3, 8, 3, 7 ]
Then the result of add_carry( a, b, 1 ) is
         head
           |
          \|/
[ 4, 2, 7, 5 ]
and the result of add_carry( a, b, 0 ) is
         head
           |
          \|/
[ 4, 2, 7, 4 ]

8. Write a function add_carry( a: int list, b: int list, carry: int ) as defined above. A template of the function is given below:

fun add_carry( a: int list, b: int list, carry: int ) =
    if a=nil andalso b=nil andalso carry=0 then 
       nil
    else 
       (* to be completed *) :: (* to be completed *);
Hint: To complete the function, you need to make use of div and mod operations, and the functions Hd and Tl.


9. Implement the function add( a: int list, b: int list) in terms of add_carry. Note that add is not a recursive function (add_carry does all the work).


10. Now, write a function Add( a: string, b: string ) that behaves as below:

- Add("123456789123456789","987654321987654321");
val it = "1111111111111111110" : string
- Add("5240006432","81238888");
val it = "5321245320" : string
Hint: Your implementation of Add has to make use of the functions add, string_to_list, list_to_string. Test your function to make sure that it gives the above results.



Next, we want to implement the function Multiply as discussed in the top of the page.


As an intermediate step, we need to introduce another function mult_carry( a: int list, d: int, carry: int ) which multiplies a list of digits by an single digit d and add the result with the carry which could be any single decimal digit. For example, let a be

               head 
                 |
                \|/ 
   [ 5, 0, 7, 3, 2 ]
, d be 8 and carry be 7. Then the result of mult_carry is
               head 
                 |
                \|/ 
[ 4, 0, 5, 8, 6, 3 ]
That is, 50732*8 + 7 = 405863.


11. Write the function mult_carry( a: int list, d: int, carry: int ) as defined above. A template of the function is given below:

fun mult_carry( a:int list, d: int, carry: int ) =
    if (a=nil orelse d=0) andalso carry=0 then 
       nil
    else 
       (* to be completed *) :: (* to be completed *);
Hint: To complete the function, you need to make use of div and mod operations, and the functions Hd and Tl. Note that the logic is similar to that of add_carry.


12. Write the function multiply( a: int list, b: int list ) which multiplies two given list of digits. For example, if a is

         head 
           |
          \|/ 
   [ 1, 2, 2 ]
and b is
         head 
           |
          \|/ 
      [ 1, 2 ]
, then the result of multiply is
         head 
           |
          \|/ 
[ 1, 4, 6, 4 ]
. That is, 122 * 12 = 1464. A template for the function is given below:
fun multiply( a: int list, b: int list ) =
    if a=nil orelse b=nil then 
       nil
    else 
       (* to be completed *);
Hint: How do we multiply two numbers say a=12345 and b=1232? We can multiply a by the lowest-ordered digit of b which is 2 to give 24690. Next, we multiply a by 123 (which is obtained by taking out the last digit from b) to give an intermediate result which we first pad it with a 0 before adding with 24690 to give the final result. To complete the details for the function multiply, you need to make use of the operator :: and the functions add, mult_carry, hd, tl.


13. Now that the function multiply( a: int list, b: int list ) has been implemented, write  the function Multiply( a: string, b: string ) that behaves as below:

- Multiply("123456789123456789","987654321987654321");
val it = "121932631356500531347203169112635269" : string
- Multiply("34567","123456789");
val it = "4267530825363" : string
- Multiply("234567891","98765");
val it = "23167097754615" : string
Test your function Multiply to see that it gives the above result.