[算法 笔记]大数相乘
今天去参加腾讯笔试,其中有一道选答题:大数相乘问题。在编写代码的过程,我突然发现以前写的原始的大数相乘是一个很简陋的源码。所以,下午找个时间重新写了一份。
大数相乘:两个超出整型限制的两个数相乘,例如,两个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 }
这里我仅仅提供了一个简单的测试,欢迎大家来指导!!