今天去參加騰訊筆試,其中有一道選答題:大數相乘問題。在編寫代碼的過程,我突然發現以前寫的原始的大數相乘是一個很簡陋的源碼。所以,下午找個時間重新寫了一份。
大數相乘:兩個超出整型限制的兩個數相乘,例如,兩個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 }
這里我僅僅提供了一個簡單的測試,歡迎大家來指導!!