[算法 筆記]大數相乘


  今天去參加騰訊筆試,其中有一道選答題:大數相乘問題。在編寫代碼的過程,我突然發現以前寫的原始的大數相乘是一個很簡陋的源碼。所以,下午找個時間重新寫了一份。

  大數相乘:兩個超出整型限制的兩個數相乘,例如,兩個50位的正數相乘。

  最簡陋的方式,就是按照乘法的計算過程來模擬計算:

       1 2

    × 3 6

   ---------- ---- 其中,上標數字為進位數值。

     71 2  --- 在這個計算過程中,2×6=12。本位保留2,進位為1.這里是一個簡單的計算過程,如果在高位也需要進位的情況下,如何處理?

    3 6

    -----------

    413  2

 

  開始比較簡陋的源碼就是基本模擬上述的乘法過程:

 1 int compute_value( const char *lhs, int lhs_start_index,
 2                    const char *rhs, int rhs_start_index,
 3                    char *result )
 4 {
 5     int i = 0, j = 0, res_i = 0;
 6     int tmp_i = 0;
 7     int carry = 0;
 8 
 9     for ( i = lhs_start_index; lhs[i] != '\0'; ++i, ++tmp_i )
10     {
11         res_i = tmp_i;  // 在每次計算時,結果存儲的位需要增加。如上述模擬過程中,第二行。 12         carry = 0;
13 
14         for ( j = rhs_start_index; rhs[j] != '\0'; ++j )
15         {
16             int tmp_lhs = lhs[i] - '0';
17             int tmp_rhs = rhs[j] - '0';
18             carry += ( result[res_i] - '0' );       // 這里需要注意,因為每次計算並不能保證以前計算結果的進位都消除,因此這里是加號。 19             carry += ( tmp_lhs * tmp_rhs );      // 並且以前的計算結果也需要考慮。 20             result[res_i++] = ( carry % 10 + '0' );
21             carry /= 10;
22         }
23 
24         while ( carry )  // 當乘數的一次計算完成,可能存在有的進位沒有處理。因此,這里對其進行處理。 25         {
26             result[res_i++] = ( carry % 10 + '0' );
27             carry /= 10;
28         }
29     }
30     result[res_i] = '\0';
31 
32     return res_i;
33 }

  上述源碼能夠完成基本的運算,比如非負數,非小數等情況。如果在傳遞的參數是不規則或不正確,例如,“  -1234”, "+1234", “- 123”。這里的處理有點類似於atoi函數(http://blog.csdn.net/v_july_v/article/details/9024123)的處理。

  因此,大數相乘是一個陷阱多多的函數,其中一個容易被忽略的陷阱就是:按照正常人的習慣,高數位存放在最左端,個位放在右端,例如 1223的字符串為“1223”,但是在上述函數計算過程中,是從左到右計算的。這是一個非常容易被忽略的錯誤,也是最致命的錯誤之一。

  整體思路如下:

  1. 檢查參數的合理性;

  2. 判斷傳遞參數是正負數;

  3. 判斷傳遞參數是否為小數;

  4. 翻轉並計算數值;

  5. 如果存在小數,需要將結果中小數點的放置;

  6. 如果計算結果為負值,則將結果設置為負值。

  全部源碼如下:

  1 #include <stdio.h>
  2 #include <stdlib.h>
  3 #include <string.h>
  4 #include <assert.h>
  5 #include <ctype.h>
  6 
  7 // 翻轉data[start...end-1]
  8 void reverse_data( char *data, int start, int end  )
  9 {
 10     char temp = '0';
 11 
 12     assert( data != NULL && start < end );
 13     while ( start < end )
 14     {
 15         temp = data[start];
 16         data[start] = data[--end];
 17         data[end] = temp;
 18         ++start;
 19     }
 20 }
 21 
 22 /**< 判斷數據中數值的合法性,以及非空格字符的起始位置。
 23  *  1. 是否均有正確的正負號,例如"+123", "-123"等
 24  *  2. 字符串中字符是否均是數字字符。
 25  *  3. 字符串開始部分放入空格是合法的,例如,”  +123“等
 26  *  4. 只有一個'.'標記
 27  **< 參數:
 28  * @data: 表示傳入字符;
 29  * @nonspace_index:非空格起點
 30  **< 返回值:若數據是合法數值,則返回1;否則返回0;
 31  */
 32 int check_logic( const char *data, int *nonspace_index )
 33 {
 34     int flag = 1;
 35     int start = 0;
 36     int point_cnt = 0;
 37 
 38     assert( data != NULL );
 39     /* PS. if data is not space(' ', '\n'), isspace() return 0. */
 40     for ( ; isspace( data[start] )!= 0
 41             && data[start] != '\0'; ++start );
 42 
 43     // 判斷數據是否為負數
 44     *nonspace_index = start;
 45     if ( data[start] == '-' || data[start] == '+' )
 46     {
 47         ++start;
 48     }
 49 
 50     /* PS. if ch is digit character, isdigit() return 1; otherwise return 0. */
 51     for ( ; data[start] != '\0'; ++start )
 52     {
 53         if ( isdigit( data[start] ) || data[start] == '.' )
 54         {
 55             // 判斷數據為小數的格式是否正確。
 56             if ( data[start] == '.' && point_cnt == 0 )
 57             {
 58                 ++point_cnt;
 59             }
 60             else if ( point_cnt > 1 )
 61             {
 62                 break;
 63             }
 64         }
 65     }
 66 
 67     // 若小數點后面無數據,則不合法
 68     if ( data[start] != '\0' )
 69     {
 70         flag = 0;
 71     }
 72 
 73     return flag;
 74 }
 75 
 76 /**< notice: 傳入到該函數的數據已經被翻轉后的數值。即,最左邊為個位,最右邊為最高位
 77  **< return: 結果數據的長度。
 78  */
 79 int compute_value( const char *lhs, int lhs_start_index,
 80                    const char *rhs, int rhs_start_index,
 81                    char *result )
 82 {
 83     int i = 0, j = 0, res_i = 0;
 84     int tmp_i = 0;
 85     int carry = 0;
 86 
 87     for ( i = lhs_start_index; lhs[i] != '\0'; ++i, ++tmp_i )
 88     {
 89         res_i = tmp_i;
 90         carry = 0;
 91 
 92         for ( j = rhs_start_index; rhs[j] != '\0'; ++j )
 93         {
 94             int tmp_lhs = lhs[i] - '0';
 95             int tmp_rhs = rhs[j] - '0';
 96             carry += ( result[res_i] - '0' );
 97             carry += ( tmp_lhs * tmp_rhs );
 98             result[res_i++] = ( carry % 10 + '0' );
 99             carry /= 10;
100         }
101 
102         while ( carry )
103         {
104             result[res_i++] = ( carry % 10 + '0' );
105             carry /= 10;
106         }
107     }
108     result[res_i] = '\0';
109 
110     return res_i;
111 }
112 
113 int has_point( char *data, int index, int *point_index )
114 {
115     int start = index;
116 
117     for ( ; data[start] != '\0'; ++start )
118     {
119         if ( data[start] == '.' )
120         {
121             *point_index = start;
122             break;
123         }
124     }
125 
126     return ( data[start] != '\0' );
127 }
128 
129 int is_neg( char *data, int *index )
130 {
131     int flag = 0;
132     int start = *index;
133     if ( data[start] == '-' || data[start] == '+' )
134     {
135         if ( data[start] == '-' )
136             flag = 1;
137         ++start;
138     }
139 
140     *index = start;
141     return flag;
142 }
143 
144 void copy_c( char * dest, const char *src )
145 {
146     while ( *src != '\0' )
147     {
148         if ( *src != '.' )
149             *dest++ = *src;
150         src++;
151     }
152 }
153 
154 int compute_decimals( char *lhs, int lhs_point_index,
155                       char *rhs, int rhs_point_index,
156                       char *result  )
157 {
158     int lhs_length = strlen( lhs );
159     int rhs_length = strlen( rhs );
160     int result_point_index = lhs_length + rhs_length;
161     int result_length = 0, i = 0;
162     char *tmp_lhs = NULL;
163     char *tmp_rhs = NULL;
164 
165     // 計算在結果中放置小數點的位置,根據的是兩個小數部分長度之和
166     // 例如,rhs = "12.345", lhs = "3.45", result = "xxx.xxxxx"
167     result_point_index -= ( lhs_point_index + rhs_point_index );
168 
169     // 分配並拷貝
170     if ( lhs_point_index )
171     {
172         tmp_lhs = (char *)malloc( sizeof(char) * lhs_length );
173         assert( tmp_lhs != NULL );
174         copy_c( tmp_lhs, lhs );
175         tmp_lhs[lhs_length - 1] = '\0';
176     }
177     else
178     {
179         tmp_lhs = lhs;
180     }
181 
182     if ( rhs_point_index )
183     {
184         tmp_rhs = (char *)malloc( sizeof(char) * rhs_length );
185         assert( tmp_rhs != NULL );
186         copy_c( tmp_rhs, rhs );
187         tmp_rhs[rhs_length - 1] = '\0';
188     }
189     else
190     {
191         tmp_rhs = rhs;
192     }
193 
194     // tmp_lhs比lhs少一個小數點
195     reverse_data( tmp_lhs, 0, lhs_length - 1 );
196     reverse_data( tmp_rhs, 0, rhs_length - 1 );
197     result_length = compute_value( tmp_lhs, 0, tmp_rhs, 0, result );
198     for ( i = result_length; i > result_point_index; --i )
199     {
200         result[i] = result[i - 1];
201     }
202 
203     result[result_point_index] = '.';
204     ++result_length;
205     result[result_length] = '\0';
206 
207     // 釋放資源
208     if ( lhs_point_index )
209     {
210         free( tmp_lhs ), tmp_lhs = NULL;
211     }
212 
213     if ( rhs_point_index )
214     {
215         free( tmp_rhs ), tmp_rhs = NULL;
216     }
217 
218     return result_length;
219 }
220 
221 // 返回結果數值的長度
222 int big_number_multiply( char *lhs, char *rhs, char *result )
223 {
224     int lhs_start_index = 0, lhs_point_index = 0;
225     int rhs_start_index = 0, rhs_point_index = 0;
226     int result_is_neg = 0;
227     int result_length = 0;
228 
229     assert( lhs != NULL && rhs != NULL && result != NULL );
230     // 檢查數據的合法性
231     if ( !(check_logic( lhs, &lhs_start_index )
232             && check_logic( rhs, &rhs_start_index )) )
233     {
234         return -1;
235     }
236 
237     // 檢查數據是否為負數
238     result_is_neg = is_neg( lhs, &lhs_start_index );
239     if ( is_neg( rhs, &rhs_start_index) )
240     {
241         result_is_neg = result_is_neg == 1 ?  0 : 1;
242     }
243 
244     // 檢查是否兩個數值中存在一個小數或兩者都是小數
245     if ( !( has_point( lhs, lhs_start_index, &lhs_point_index )
246             && has_point( rhs, rhs_start_index, &rhs_point_index ) ) )
247     {
248         reverse_data( lhs, lhs_start_index, strlen(lhs) );
249         reverse_data( rhs, rhs_start_index, strlen(rhs) );
250         result_length = compute_value( lhs, lhs_start_index,
251                                        rhs, rhs_start_index, result );
252         reverse_data( lhs, lhs_start_index, strlen(lhs) );
253         reverse_data( rhs, rhs_start_index, strlen(rhs) );
254     }
255     else // 一個數值中有小數部分
256     {
257         result_length = compute_decimals(
258                           lhs + lhs_start_index, lhs_point_index - lhs_start_index + 1,
259                           rhs + rhs_start_index, rhs_point_index - rhs_start_index + 1,
260                           result );
261     }
262     if ( result_is_neg )
263         result[result_length++] = '-';
264     reverse_data( result, 0, result_length );
265     result[result_length] = '\0';
266 
267     return result_length;
268 }
269 
270 int main()
271 {
272     char lhs[] = "-1.235";
273     char rhs[] = "    3.456";
274     char result[40];
275 
276     memset( result, '0', sizeof(result) );
277 
278     big_number_multiply( lhs, rhs, result );
279     printf( "%s\n", result );
280     return 0;
281 }
View Code

 

  這里我僅僅提供了一個簡單的測試,歡迎大家來指導!!


免責聲明!

本站轉載的文章為個人學習借鑒使用,本站對版權不負任何法律責任。如果侵犯了您的隱私權益,請聯系本站郵箱yoyou2525@163.com刪除。



 
粵ICP備18138465號   © 2018-2025 CODEPRJ.COM