Working With Number – Infinity Multiplication – Optimised The Code – Perl

In the previous two chapters of the “Infinity Multiplication” series for Perl, I had demonstrated the methods and the programming codes for multiplying together two numbers that were stored as string type data, and the equation was processed at the rate of one digit at a time. If you need a reference on the procedures for the multiplication equation, please refer to chapter one and two of the “Infinity Multiplication” series. The chapters’ names are “Infinity Multiplication – Beyond integer” and “Infinity Multiplication – Decimal, Precise Float Calculation”.

The previous two chapters’ programming codes are error proof. This is because the programming codes for the previous chapters were built to process the equation at the rate of only one digit at a time. Besides that, we were also storing the answer for each cycle of multiplication on a separate temporary answer string before joining the temporary answer string to the final output string. By having a temporary answer string, it is easier to inspect if there are any errors in the formula or in the code. Nonetheless, the programming codes of the previous chapters are inefficient and should not be used in a production environment.

In this chapter, I will demonstrate what in my opinion consider to be the most optimized way for optimizing the previous chapters’ programming codes and methods. By optimizing the methods and the codes, our program can execute efficiently, and therefore, we can utilize the program for the production environment. Same as the previous chapters, the source code of this chapter was tested against hundred of thousand plus test cases. With this article, I also included the source code for testing all the previous chapters’ programming codes and including the code of this chapter. I also did a benchmark test for this production version of infiX versus the previous versions of infiX. Note that infiX is the name of the program that is the programming code examples for this chapter and the all the previous chapters.

Optimizing The Code

Before further discussion in regard to how to optimize the program to operate more efficiently, I will first present the results of the benchmark test for this version of infiX versus the previous version of infiX. When testing for how fast the program executes, I produced two strings of digits, each string contained forty digits. The strings are then passed into each version of infiX 100,000 times, and the time it took for each version of infiX to complete the calculation was recorded as the benchmark score. This test ran twice and the average result between the two tests is used as the final benchmark score. Note that both versions of infiX were calculating the same two strings of digits 100,000 times.

This version of infiX for Perl took 22.333 seconds to compute the results for 100,000 cases, and the test strings didn’t have a fractional part. With the decimal, the program took 25.012 seconds to compute the results 100,000 times. For a comparison of the execution time, the previous version of infiX took 257.169 seconds to compute the results for 100,000 cases, and the test string didn’t have a fractional part. When there is a fractional part, the previous version took 258.553 seconds to compute the result for 100,000 test cases.

What are the changes in the methods and in the programming code that give the program the ability to operate more than ten times faster? Instead of processing one digit at a time, the program was converted to process the equation at the rate of five digits at a time. For a summary of each multiplication cycle, the previous version of infiX had to convert a digit that was stored as string type data to an integer type data twice, convert the result value to string type data, stores the result value of the multiplication procedure to a temporary answer string, add the temporary answer string to the final output string, and calculate the carry-over values for both, the addition and the multiplication procedures. By converting the program to compute the equation at the rate of five digits at a time, we would still have the same procedures for each multiplication cycle. Nonetheless, instead of only consuming a single digit from both numbers per multiplication cycle, each of our multiplication cycles would now consume five digits. Thus, any single process in the multiplication cycle would be applied to a block of five digits and not just one. By processing more digits per cycle, the program saves processing time from all its sub-process of a multiplication cycle.

Besides processing more digits per cycle, the temporary answer string is now removed and the program would only use the single output string for storing and processing all the result values. By removing the temporary answer string, the program would also save memory usage, and therefore, requires far less memory to operate.

Besides the above changes, the “for” loop was converted into the “while” loop. The “while” loop was benchmarked to execute faster than the “for” loop. The substr() function was chosen due to its speed and execution time. For this version of infiX that is written in Perl, I do not have an alternative option for trimming the left and right of the string. Therefore, regex was the only choice.

Although this version of infiX only processes five digits at a time, this number may have to be configurated to a lower amount if we are truly working in a 32 bits environment. This is because the maximum value that a 32 bits integer can hold is 2,147,483,647. Which is about 10 digits in length. For a multiplication equation, five digits multiply by another five digits have the potential to produce a maximum result value that is ten digits in length and that value is 9,999,999,999. This is a value that is a little bit larger than the maximum value that a 32 bits integer can hold. Thus, if truly working in a 32 bits environment, the number of digits to be processed per cycle should be configurated at the rate of four digits at a time.

When it comes to the formula for multiplying five digits at a time, the formula is similar to the formula of multiplying one digit at a time. Nonetheless, when writing the result to the output string it can be a complicated task. This simply because the program would now operate without a temporary answer string. Before going into the examples and the explanations of the programming code, let’s first look at the new formula for the multiplication procedures.

With the formula above in context, we can see that we are multiplying all the digits in A string to the digits of B string in a block of five digits at a time. Each block of five digits in A string is multiplied by the first block of five digits in B string, then to the second block, then to third and so on if there is. For each time when we multiplied a block of five digits from A to the first block of five digits in B, starting from right to left of the answer string, we would only store a block of five digits at a time to the answer string. When there are more than five digits in the result value then all the digits after the fifth digits is to be kept as the carry-over value for the next equation. When storing the result into the answer string, the first five digits that are the rightmost in the result value would be the digits that are to be stored into the answer string. The sixth digits and beyond would be kept as the carry-over value.

When it comes to the procedure of when we are adding the carry-over value to the result value of the equation. Similar to the above, the first five most digits that are the rightmost in the result is the result value, digits that are beyond that is the carry-over value.

After multiplying all the digits in A string to the first block of five digits in B string, we would then multiply all the digits in A string to the next block of five digits in B string. When we are moving over to the next block of five digits in B string, our starting position for adding the equation’s result value into the answer string would move over to the left by one block that is five digits in length. Every time when the equation is moving to another block of five digits in B string, the starting position in the answer string would also move over to the left by one block of digits. For the procedure of adding the result value of an equation into the answer string, the rules from above that are in regard to the carry-over values, and how many digits are the result and how many digits are the carry-over value would also apply to this procedure.

When it comes to the variables that we need in the program, the first variable our program needs is a variable that store a value that represents how many digits that the program is processing per multiplication cycle. This value can extend the flexibility of the program. If we were to work in an environment where the integer data type is stored in larger bit blocks then we can increase this value so that the program can process more digits per cycle of calculation. On the other hand, in the event where we have to work in a more restricted environment and integer values are stored in smaller bit blocks then we can decrease this value to adapt the program to the environment. For the naming context, the mentioned variable is named “digit”.

The second variable we would need is a variable that will store a value that is the index count of how many times we had read from B string. In the circumstance of this article, we know that each time we read a string, we are reading a block of five digits. When it comes to the ending of the strings, the amount of how many digits that are left for the program to read from can vary. Nonetheless, this program was built to considers the variation on the number of digits in the final block of digits for both strings. For the naming context, the variable for storing the value of the read count for B string is named “posr”.

Besides the above variables, the program would also need a variable that stores a value that represents the position that the program is working on in the answer string. That variable is named “posw”. For a fourth new variable, the variable is named “prepos”. When utilizing the substr() function, the behavior of the substr() function is that when a negative value is passed into the substr() function, then the substr() function will treat that value as a position in the string counting from right to left, with negative one being the rightmost position. This is the opposite of when a positive value is passed into the substr() function. Since we are working from right to left in the strings, for some procedures, it is easier to use negative values. Therefore, a negative version of the value that is stored in the “digit” variable is assigned to the “prepos” variable to enhance the simplicity for the equation. This below is the variables that we need to declare outside of the calculation loop.

For every time the program process a position in B string, the program would have to process all the positions in A string. Thus, the outer loop will stay at a position in B string until all the position in A string is read, and after each cycle, the outer loop will move the read point for B string by five positions. For the inner loop, after processing a position in A string, the loop will also move the read point for A string by five positions.

To get five digits from B string, we can use the substr() function. For the usage of the substr() function, the function can take three arguments as input. The first argument is the string itself. The second argument should be an integer value, and the substr() function treats that value as the position of where to get the first character from. If the second argument’s value is a value of zero or is a positive value then the substr() function treats that value as a position that is counting from left to right. If the second argument’s value is a negative value then the substr() function will treat that value as a position that is counting from right to left with -1 being the leftmost position.

For the third argument, the substr() function will take an integer value, and the third argument is an optional one. If the third argument is omitted then the substr() function will extract all the characters from the position that was defined by the value of the second argument to the end of the string. If the third argument’s value is a negative value then the substr() function will treat that value as a position that is counting from right to left. The substr() function will then extract all the characters from and including the position that was defined by the second argument to the character that is just before the position that was defined by the third argument. Counting from left to right of the string, if the position that was defined by the third argument somehow become before the position that was defined by the second argument, then the substr() function will return an empty string. If the third argument’s value is a positive value then substr() will treat that value as the number of characters that the function should extract from the string, starting from the position that was defined by the second argument. If the string contains fewer characters than the number of characters that the substr() function should extract, then the function will extract all that is available.

When it comes to utilizing the substr() function, The first value that we are passing as the second argument is the current position of where we are at in B string subtract to the number of digits that the program is processing per cycle. Notice that we do not subtract the string’s length to a value of one to get the current position. This is because if we were to extract five rightmost digits in a string that is 20 positions long, although the last position is position 19 if to extract five digits we would have to start at position 15. Therefore, in the example, we can just utilize the length of the string, which is a value of 20, and subtract that value to a value of 5 to get the the starting position, which would be position 15. Also, we do not want a negative value as the second argument for the substr() function. This event occurs when the last block of digits contains less than the number of digits that the program is processing per cycle. Thus, if the value that we are going to pass as the second argument is a negative value then we would convert that value to a value of zero, which is the leftmost and is the first position in a string.

The value that we want to pass as the third argument for the substr() function is how many digits we want to get. This value is defined by the “digit” variable. Nonetheless, when comes to the last block of digits in either string A or B and the digits block has fewer digits than the amount that was defined by the “digit” variable, then we would extract the digits that we had already extracted in the previous loop cycle, and therefore, the equation would produce an erroneous result. Thus, if the position that we are in subtract to a value of zero is smaller than the value of the “digit” variable, then we would use the position that we are in and subtract that value to a value of zero as the value that we will pass as the substr() function’s third argument. The digits that we are extracting from the string would then be converted into an integer value. Nonetheless, if the substr() function did not get anything from the string then the int() function will produce an integer value of zero for the empty value that was returned by the substr() function.

When we are executing the inner loop for the calculating procedures and when the answer string did not yet contain a value, we would simply place the result value of the procedures into the output string bases on the following conditions. When it comes to processing the result value of the current procedure, one of the values that we have to evaluate for is the carry-over value. To get the carry-over value from the result value, we can utilize the substr() function on the result value to get the character from the zero position to five positions away from the rightmost of the result value. To do this, we pass a value of zero as the second argument for the substr() function, then we pass the “prepos” variable’s value as the third argument for the substr() function. If the previous statement fails to produce any digits, we would have to assign a zero value in place. This is to ensure that the int() function would correctly return a value of zero as intended to be. When it comes to storing the carry-over value, the carry-over value for the multiplication procedure is assigned to a variable that is named “carryOverM”.

When it comes to appending the result value to the answer string, we would always append the result value in the format of a digits block that is five digits in length. Therefore, we would have to append to the result a number of zeroes for the event of when the result value contains less than five digits. The following examples will demonstrate the circumstance of how the result value can have less than five digits. For the first example, we have an equation of 1 00125 x 1 00001. In that equation, when executing the equation, the first block of five digits from A multiply by the first block of five digits of B would translate into an equation of 1 x 125. The result value of the equation would be a value of 125. If we do not append zeroes to the beginning of the result value to make up for the missing length then when it comes to the next calculation, there will only be a value of one that is going to be added to the beginning of the result value. Which will give us a value of 1125 as the result value for the equation instead of the correct answer of 1 00125.

To be able to always consistently produce a result value that is five digits in length, we add four zeroes to the left side of the result value. We then utilize the substr() function to extract the five rightmost digits from the just modified result value. For adding zeroes to the result value, we would only execute this procedure after when we had completed the evaluation of the result value for any carry-over value.

After multiplying all the digits block of A string to the first block of digits in B string, we then append any carry-over value to the leftmost of the answer string. The principle of a consistent number of digits in a block of digits would also apply to this procedure. Take note here that when I subtract the index count for A string, of which the value is held by a variable that named “ai”, it can also be done by subtracting “ai” to the value of the “digit” variable as mentioned earlier. This code here is just for the demonstration purpose of which showed that we are moving by five positions per cycle. Nonetheless, for easy customization, this can be changed to “ai – digit”, and when modifying the value of “bi”, it can also be changed to “bi – digit”.

The previous procedures are for when the answer string did not yet contain a value. However, it can get a bit complicated for when the answer string does contain a value. Each time we read a digits block in A string, we would have to calculate the positions of where we are working on in the answer string. Besides the working on position, the starting position for the answer string would move over to the left by one digits block for each digits block that we read in B string. Also, the position that we are working on in the answer string would also move over to the left by one digits block after we process a digits block from A string.

To calculate the position that we are working on in the answer string, we would first calculate the starting position of the answer string. To get the starting position of the answer string, we multiply how many times we had read from B string to the value of the “prepos” variable, which is just the negative version of the “digit” variable. After getting the starting position of the answer string, we then multiply the number of times we had read from A string to the value of the “prepos” variable. Then we add the result value of that equation to the value of the starting position of the answer string.

For this time of multiplying a digits block from A string to the digits block in B string, we have to add the result value of the equation to a value that exists in the position of where we are working on in the answer string. The result value for this equation is stored in a temporary placeholder, the temporary placeholder is the variable with the name “tempadd”. To get the digits from the answer string, we will utilize the substr() function. For the substr() function, we use a value that is the value of the current position that we are working on in the answer string in negative and add that value to the value of the “prepos” variable. The previous value is then passed to the substr() function as the second argument. For the third argument, we use the value that is the position of where we are currently at in the answer string.

If the tempadd variable holds a value that is more than five digits in length then we would produce a carry-over value for the addition procedure. The carry-over value for our addition procedure is held by a variable that is named carryOverA. For an addition equation, the carry-over value can never be more than a value of one if we are only adding two sets of digits together. Thus, our carry-over value our addition procedure can only be a value of one when the result value contains a value that is more than five digits in length. Else, we will assign a zero value to the carry-over value. The value of the carryOverM and the carryOverA variable will be added to the result value of the equation of next loop execution.

After producing a value for the tempadd variable, we would then place the value of the tempadd variable into our answer string. When it comes to placing the tempadd’s value into the answer string, we first get all the digits from the beginning of the answer string to the position that is before the position of where we grabbed the first digit from the answer string. We then place our tempadd’s value to the right side of the previous value. Then we get the digits from the position that is after the position of the last digit that we grabbed from the answer string to the end of the answer string. We place this value on the right side of where we placed the tempadd’s value. All the digits that we pieced up together with the tempadd’s value are assigned as a new value to the answer string.

After completely multiplying all the digits in A string to a set of digits in B string, we would add the value of the carryOverM and carryOverA variable together, and place this value to the left of the answer string. The below code block demonstrate the above procedures into programming statements for Perl.

As for testing and debugging the infiX program, the code block below is what I used to test the previous version of InfiX against the native built-in multiplication feature of the Perl’s platform. For the previous version of infiX, the program was built to process the multiplication equation at the rate of only one digit at a time. Therefore, if the program always correctly produces result values for equations that involve smaller strings of digits, then the program would also be able to consistently produces correct result values for larger strings of digits. I also tested the infiX program against Ruby’s native multiplication feature. As it seems, Ruby’s native multiplication feature can also calculate very large numbers.

When it comes to numbers that do have a fractional part, do take into consideration that the native built-in calculating feature would not be able to provide the actual precise floating point calculation. The tester code below will produce two random numbers for each test case. The tester will then multiplying the numbers together using the native multiplication feature of Perl. The tester will then pass the numbers as string type data into the infiX function. If there is a mismatch between the two result values, then the tester will print the test case information.

To use this tester, simply look for version two of infiX, which included in the chapter that is called “Decimal, Precise Float Calculation” for Perl. Then place this tester code with the program. The number of test cases that the tester will execute was preset at a value of 5000. This value can be increased or decreased as necessary. For Perl, it is safe to set the number of cases to run at less than or equal to 100,000 cases at a time. Otherwise, it might take sometimes to get back the results value. This code block below is the tester code for the version two of infiX.

When it comes to testing this version of infiX, I tested the result values of this version of infiX to the result values of the previous version of infiX. Since the previous version of infiX was already thoroughly tested, this version’s result value would be correct if matches the result values of the previous version. The previous version of infiX does take a long time to produce result values. Nonetheless, as a prototype, I built the previous version as a demonstrator for the articles that I wrote and also as a tester for testing future releases of the infiX program.

To use the tester code below, simply place this version of infiX and the version two of infiX in the same file and with the tester code. Rename one of the infiX function to infiX2. This tester will produce five blocks of random digits that are before the decimal for each string of digits. The random blocks of digits can be anywhere between one or five digits. The tester will then pad 0 to 15 zeroes behind the decimal, and then the tester will place three blocks of random numbers to the right of the zeroes that were just added to the string. After that, the tester will place another 0 to 15 zeroes and then the last two blocks of random digits to the right side of the previous 3 block of random digits. It is a recommendation that this tester should not be set to execute more than 50,000 cases at a time. It can take a very long time to produce the results. This is because the previous version of InfiX does take a long time to produce an answer.