今天去参加腾讯笔试,其中有一道选答题:大数相乘问题。在编写代码的过程,我突然发现以前写的原始的大数相乘是一个很简陋的源码。所以,下午找个时间重新写了一份。
大数相乘:两个超出整型限制的两个数相乘,例如,两个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函数()的处理。
因此,大数相乘是一个陷阱多多的函数,其中一个容易被忽略的陷阱就是:按照正常人的习惯,高数位存放在最左端,个位放在右端,例如 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 }
这里我仅仅提供了一个简单的测试,欢迎大家来指导!!